sigma_enum/lib.rs
1//! `sigma_enum` is a procedural macro that allows a family of types to be
2//! summed into an enum and pattern matched on. It implements Σ types, also
3//! known as dependent pairs. The macro exports a set of derive macros that
4//! allow runtime values to be lifted to compile time.
5//!
6//! ## Quick start
7//!
8//! ```rust
9//! use sigma_enum::sigma_enum;
10//!
11//! #[derive(Debug)]
12//! struct Bytes<const N: usize>([u8; N]);
13//!
14//! // Define sigma enum
15//! #[sigma_enum(generic(Bytes<usize>))]
16//! enum BytesEnum {
17//! __(usize), // A standalone type
18//! #[sigma_enum(expand(N = 0..10))]
19//! __(Bytes<N>), // Types indexed by a const generic
20//! }
21//!
22//! let n: usize = "8".parse().unwrap();
23//! // Construct based on a runtime value
24//! let bytes = bytes_enum_construct!(Bytes::<?n>(Bytes([0x41; n]))).unwrap();
25//!
26//! // Match on the const generic in the type
27//! let displayed = bytes_enum_match!(match bytes {
28//! usize(bytes) => format!("usize: {bytes}"),
29//! Bytes::<?N>(bytes) => format!("{N} bytes: {bytes:?}"),
30//! });
31//! ```
32//!
33//! ## Basic usage
34//!
35//! The most basic use for `sigma_enum` is applying it to an enum of tuple
36//! struct variants, each of which has one value.
37//!
38//! ```rust
39//! # use sigma_enum::sigma_enum;
40//! #[sigma_enum]
41//! enum Numeric {
42//! __(i32),
43//! __(i64),
44//! }
45//! ```
46//!
47//! The names of the variants will be automatically generated if the provided
48//! names start with an underscore, and the provided names will be used
49//! otherwise.
50//!
51//! Generating an enum that simulates a type that depends on a const generic
52//! value can be done by using the const generic in the type, or in shorthand
53//! with the `expand` attribute. Valid specifications for `expand` metavariables
54//! are literals, ranges, and arrays of those.
55//!
56//! In order to use const generics in an enum, the attribute macro should be
57//! annotated with the const generic types used within using the `generic`
58//! attribute. If not, certain functionality will be unavailable. Non-const
59//! generics can be annotated with `_`.
60//! Since the const generic types are used in declarative macros, fully
61//! qualified names should be used.
62//!
63//! ```rust
64//! # use sigma_enum::sigma_enum;
65//! struct Array<T, const N: usize>([T; N]);
66//!
67//! #[sigma_enum(generic(Array<_, ::std::primitive::usize>))]
68//! enum BytesEnum {
69//! #[sigma_enum(expand(N = 0..3))]
70//! __(Array<u8, N>),
71//! }
72//!
73//! // equivalent to
74//! #[sigma_enum(generic(Array<_, ::std::primitive::usize>))]
75//! enum BytesEnum2 {
76//! __(Array<u8, 0>),
77//! __(Array<u8, 1>),
78//! __(Array<u8, 2>),
79//! }
80//! ```
81//!
82//! Types used as enum variants for now must only be written with identifiers,
83//! literals, and `<>`.
84//!
85//! ### Renaming variants
86//!
87//! In addition to specifying the name of a variant with the name used in the
88//! enum, renaming can also be done with the `rename` attribute. Used on a
89//! standard variant, it can be used to select a name for the variant. Used on a
90//! variant with the `expand` attribute, a format string can be provided and the
91//! metavariables used will be interpolated into it.
92//!
93//! ```rust
94//! # use sigma_enum::sigma_enum;
95//! struct Array<T, const N: usize>([T; N]);
96//!
97//! #[sigma_enum(generic(Array<_, ::std::primitive::usize>))]
98//! enum BytesEnum {
99//! #[sigma_enum(expand(N = 0..3), rename = "ByteArray{N}")]
100//! __(Array<u8, N>),
101//! }
102//!
103//! // equivalent to
104//! #[sigma_enum(generic(Array<_, ::std::primitive::usize>))]
105//! enum BytesEnum2 {
106//! ByteArray0(Array<u8, 0>),
107//! ByteArray1(Array<u8, 1>),
108//! ByteArray2(Array<u8, 2>),
109//! }
110//! ```
111//!
112//! ### Renaming types
113//!
114//! The only types allowed in variant specifications are those written as
115//! unqualified identifiers, optionally with generic parameters. For qualified
116//! type names, use the `alias` attribute.
117//!
118//! ```rust
119//! # use sigma_enum::sigma_enum;
120//! mod inner {
121//! pub struct Foo;
122//! }
123//!
124//! #[sigma_enum(alias(Foo = inner::Foo))]
125//! enum Foo {
126//! __(Foo),
127//! }
128//! ```
129//!
130//!
131//! ## Generated items
132//!
133//! ### Macros
134//!
135//! `sigma_enum` generates several macros for each enum.
136//!
137//! The first is the construction macro. This allows for the construction of
138//! values whose types involve const generics even when the values of the const
139//! generics only exist at runtime. Associated to a type `T`, the construction
140//! macro returns a value of type `Option<T>`. Metavariables used in the type
141//! specification must be preceded with `?`.
142//!
143//! ```rust
144//! # use sigma_enum::sigma_enum;
145//! struct Bytes<const N: usize>([u8; N]);
146//!
147//! #[sigma_enum(generic(Bytes<usize>))]
148//! enum BytesEnum {
149//! #[sigma_enum(expand(N = 0..3))]
150//! __(Bytes<N>),
151//! }
152//!
153//! let n: usize = 1;
154//! let bytes = bytes_enum_construct!(Bytes::<?n>(Bytes([0x41; n]))).unwrap();
155//! ```
156//!
157//! Dual to the construction macro is the match macro. This facilitates the use
158//! of the enum as any of its contained types. Metavariables used in the type
159//! specification must be preceded with `?`.
160//!
161//! ```rust
162//! # use sigma_enum::sigma_enum;
163//! #[derive(Debug)]
164//! struct Bytes<const N: usize>([u8; N]);
165//!
166//! #[sigma_enum(generic(Bytes<usize>))]
167//! enum BytesEnum {
168//! #[sigma_enum(expand(N = 0..3))]
169//! __(Bytes<N>),
170//! }
171//!
172//! fn displayed(bytes: BytesEnum) -> String {
173//! bytes_enum_match!(match bytes {
174//! Bytes::<?N>(bytes) => format!("{N} bytes: {bytes:?}"),
175//! })
176//! }
177//! ```
178//!
179//! ## Traits
180//!
181//! The `sigma_enum` macro also generates a conversion trait for each enum with
182//! methods for constructing values of the enum of a known variant and
183//! extracting a value of a known type from the enum. Helper methods on the enum
184//! for extraction are also generated.
185//!
186//! ```rust
187//! # use sigma_enum::sigma_enum;
188//! struct Bytes<const N: usize>([u8; N]);
189//!
190//! #[sigma_enum(generic(Bytes<usize>))]
191//! enum BytesEnum {
192//! #[sigma_enum(expand(N = 0..3))]
193//! __(Bytes<N>),
194//! }
195//!
196//! let bytes_enum = Bytes([0x41; 2]).into_bytes_enum(); // uses IntoBytesEnum trait
197//! let bytes: &Bytes<2> = Bytes::<2>::try_from_bytes_enum(&bytes_enum).unwrap(); // uses IntoBytesEnum trait
198//! let bytes: Bytes<2> = bytes_enum.extract_owned().unwrap();
199//! ```
200//!
201//! `From`, `Into`, `TryFrom`, and `TryInto` will also be implemented between
202//! the enum and all types it contains as variants.
203//!
204//! ## Public API generation
205//!
206//! Marking the enum `pub` will export the generated macros and traits as
207//! part of the public API.
208//!
209//! <div class="warning">
210//!
211//! For enums whose macros you intend to use in
212//! another module of the crate, you must add the `path` attribute that contains
213//! the absolute path to the module.
214//!
215//! For public enums, you may not use the generated macros in the same crate
216//! they were defined in. This is a limitation of Rust's macro system
217//! (cf. [issue #52234](https://github.com/rust-lang/rust/pull/52234)).
218//! In this case, adding the `path` attribute will generate two sets of macros:
219//! one that is exported at the crate root, and another usable in the definition
220//! crate whose names are suffixed by `_crate`.
221//!
222//! ```rust
223//! pub mod inner {
224//! # use sigma_enum::sigma_enum;
225//! pub struct Foo;
226//!
227//! #[sigma_enum(path = crate::inner)]
228//! pub enum FooEnum {
229//! __(Foo),
230//! }
231//! }
232//!
233//! # fn main(){
234//! inner::foo_enum_construct_crate!(Foo(inner::Foo));
235//! // foo_enum_construct!(Foo(inner::Foo)); // cannot refer to this macro
236//! # }
237//! ```
238//!
239//! </div>
240//!
241//! ### Renaming generated items
242//!
243//! Generated items can be renamed and docstrings can be provided with the
244//! following attributes:
245//!
246//! | Item | Attribute name |
247//! | ---------------------------------- | ----------------------- |
248//! | Construction macro | `macro_construct` |
249//! | Match macro | `macro_match` |
250//! | Enum trait | `into_trait` |
251//! | Enum trait construction method | `into_method` |
252//! | Enum trait extraction method | `try_from_method` |
253//! | Enum trait owned extraction method | `try_from_owned_method` |
254//! | Enum trait mut extraction method | `try_from_mut_method` |
255//! | Enum extraction method | `extract_method` |
256//! | Enum owned extraction method | `extract_owned_method` |
257//! | Enum mut extraction method | `extract_mut_method` |
258//! | Enum trait error | `try_from_error` |
259//!
260//! ```rust
261//! # use sigma_enum::sigma_enum;
262//! #[sigma_enum(
263//! macro_construct(name = make_numeric, docs = "Make a numeric value."),
264//! macro_match(name = match_numeric, docs = "Match a numeric value.")
265//! )]
266//! enum Numeric {
267//! __(i32),
268//! __(i64),
269//! }
270//! ```
271//!
272//! ## Additional information
273//!
274//! Derive macros and other item attributes will work when placed below the
275//! `sigma_enum` macro. Variant attributes will be copied to every instance of
276//! the variant if expanded.
277
278/// The macro.
279///
280/// See the crate root for detailed documentation.
281pub use sigma_enum_macros::sigma_enum;
282
283#[cfg(test)]
284mod tests {
285 use super::sigma_enum;
286
287 pub struct A;
288 pub struct B;
289
290 #[sigma_enum(path = crate::tests)]
291 pub enum AbEnum {
292 __(A),
293 __(B),
294 }
295
296 struct FooType<T>(T);
297 impl FooType<A> {
298 fn is_a(&self) -> bool {
299 true
300 }
301 }
302 impl FooType<B> {
303 fn is_a(&self) -> bool {
304 false
305 }
306 }
307
308 #[sigma_enum(alias(Foo = FooType))]
309 enum FooEnum {
310 __(Foo<A>),
311 __(Foo<B>),
312 }
313
314 #[derive(Debug, Clone, Copy)]
315 struct Mu<const N: usize>([(); N]);
316 #[derive(Debug, Clone, Copy)]
317 struct Nu<M>(M);
318
319 #[sigma_enum(
320 generic(Mu<usize>),
321 macro_match(name = nu_match, docs =
322"A macro to match Nu."
323 ),
324 )]
325 #[derive(Debug, Clone, Copy)]
326 #[non_exhaustive]
327 enum NuEnum {
328 #[sigma_enum(expand(N = 0..=3))]
329 __(Nu<Mu<N>>),
330 NuMu5(Nu<Mu<5>>),
331 #[sigma_enum(expand(N = [7..9, 11]), rename = "NuMu{N}_Big")]
332 __(Nu<Mu<N>>),
333 }
334
335 #[sigma_enum]
336 enum NuEnumNoGen {
337 #[sigma_enum(expand(N = 0..=3))]
338 __(Nu<Mu<N>>),
339 __(Nu<Mu<5>>),
340 #[sigma_enum(expand(N = [7..9, 11]))]
341 __(Nu<Mu<N>>),
342 }
343
344 #[sigma_enum]
345 enum EmptyEnum {}
346
347 // test if those in inner modules compile outside
348 pub mod inner {
349 use crate::sigma_enum;
350 use crate::tests::A;
351 use crate::tests::B;
352
353 #[sigma_enum(path = crate::tests::inner)]
354 pub enum AbEnumI {
355 __(A),
356 __(B),
357 }
358 }
359
360 #[test]
361 fn match_ab_enum() {
362 assert_eq!(
363 ab_enum_match_crate!(match ab_enum_construct_crate!(A(A)).unwrap() {
364 A(_ab) => 1,
365 B(_ab) => 2,
366 }),
367 1
368 );
369
370 assert_eq!(
371 ab_enum_match_crate!(match ab_enum_construct_crate!(A(A)).unwrap() {
372 B(_ab) => 1,
373 _ab => 2,
374 }),
375 2
376 );
377 }
378
379 #[test]
380 fn match_ab_enum_i() {
381 assert_eq!(
382 inner::ab_enum_i_match_crate!(match inner::AbEnumI::A(A) {
383 A(_ab) => 1,
384 B(_ab) => 2,
385 }),
386 1
387 );
388
389 assert_eq!(
390 inner::ab_enum_i_match_crate!(match inner::AbEnumI::A(A) {
391 B(_ab) => 1,
392 _ab => 2,
393 }),
394 2
395 );
396 }
397
398 #[test]
399 fn match_foo_enum() {
400 assert_eq!(
401 foo_enum_match!(match FooEnum::Foo_B(FooType(B)) {
402 Foo::<A>(_foo) => 1,
403 Foo::<B>(_foo) => 2,
404 }),
405 2
406 );
407
408 assert!(foo_enum_match!(match FooEnum::Foo_A(FooType(A)) {
409 Foo::<?T>(foo) => foo.is_a(),
410 Foo::<A>(_foo) => false, // intentionally does not match
411 }),);
412
413 assert!(foo_enum_match!(match FooEnum::Foo_A(FooType(A)) {
414 foo => foo.is_a(),
415 }),);
416 }
417
418 #[test]
419 fn match_nu_enum() {
420 // let numu2 = NuEnum::Nu_Mu_2(Nu(Mu([(); 2])));
421 let n = 2;
422 let numu2 = nu_enum_construct!(Nu::<Mu<?n>>({
423 let arr = [(); n];
424 Nu(Mu(arr))
425 }))
426 .unwrap();
427
428 assert_eq!(
429 nu_match!(match numu2 {
430 Nu::<Mu<0>>(_nu) => 9,
431 Nu::<Mu<?N>>(_nu) => N,
432 _nu => 99,
433 }),
434 2
435 );
436
437 // check const in there
438 assert_eq!(
439 nu_match!(match numu2 {
440 Nu::<Mu<?N>>(_nu) => {
441 let _mu_arr = [(); N];
442 N
443 }
444 }),
445 2
446 );
447
448 // make sure types don't blow up the let
449 assert_eq!(
450 nu_match!(match numu2 {
451 Nu::<?T>(_nu) => 2,
452 }),
453 2
454 );
455 }
456
457 #[test]
458 fn match_nu_enum_no_gen() {
459 assert_eq!(
460 nu_enum_no_gen_match!(match (NuEnumNoGen::Nu_Mu_2(Nu(Mu([(), ()])))) {
461 Nu::<Mu<0>>(_nu) => 9,
462 Nu::<Mu<?N>>(_nu) => N,
463 }),
464 2
465 );
466 }
467}