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}