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//! ### Docstrings for variants
113//!
114//! Docstrings can be provided as standard doc comments below the `sigma_enum`
115//! variant attribute. For a variant with the `expand` attribute, a docstring
116//! template with the same syntax as the renaming template can be provided with
117//! the `docs` attribute.
118//!
119//! ```rust
120//! # use sigma_enum::sigma_enum;
121//! struct Array<T, const N: usize>([T; N]);
122//!
123//! #[sigma_enum(generic(Array<_, ::std::primitive::usize>))]
124//! enum BytesEnum {
125//!     #[sigma_enum(expand(N = 0..3), docs = "{N} bytes.")]
126//!     __(Array<u8, N>),
127//! }
128//! ```
129//!
130//! ### Renaming types
131//!
132//! The only types allowed in variant specifications are those written as
133//! unqualified identifiers, optionally with generic parameters. For qualified
134//! type names, use the `alias` attribute.
135//!
136//! ```rust
137//! # use sigma_enum::sigma_enum;
138//! mod inner {
139//!     pub struct Foo;
140//! }
141//!
142//! #[sigma_enum(alias(Foo = inner::Foo))]
143//! enum Foo {
144//!     __(Foo),
145//! }
146//! ```
147//!
148//!
149//! ## Generated items
150//!
151//! ### Macros
152//!
153//! `sigma_enum` generates several macros for each enum.
154//!
155//! The first is the construction macro. This allows for the construction of
156//! values whose types involve const generics even when the values of the const
157//! generics only exist at runtime. Associated to a type `T`, the construction
158//! macro returns a value of type `Option<T>`. Metavariables used in the type
159//! specification must be preceded with `?`.
160//!
161//! ```rust
162//! # use sigma_enum::sigma_enum;
163//! struct Bytes<const N: usize>([u8; N]);
164//!
165//! #[sigma_enum(generic(Bytes<usize>))]
166//! enum BytesEnum {
167//!     #[sigma_enum(expand(N = 0..3))]
168//!     __(Bytes<N>),
169//! }
170//!
171//! let n: usize = 1;
172//! let bytes = bytes_enum_construct!(Bytes::<?n>(Bytes([0x41; n]))).unwrap();
173//! ```
174//!
175//! Dual to the construction macro is the match macro. This facilitates the use
176//! of the enum as any of its contained types. Metavariables used in the type
177//! specification must be preceded with `?`.
178//!
179//! ```rust
180//! # use sigma_enum::sigma_enum;
181//! #[derive(Debug)]
182//! struct Bytes<const N: usize>([u8; N]);
183//!
184//! #[sigma_enum(generic(Bytes<usize>))]
185//! enum BytesEnum {
186//!     #[sigma_enum(expand(N = 0..3))]
187//!     __(Bytes<N>),
188//! }
189//!
190//! fn displayed(bytes: BytesEnum) -> String {
191//!     bytes_enum_match!(match bytes {
192//!         Bytes::<?N>(bytes) => format!("{N} bytes: {bytes:?}"),
193//!     })
194//! }
195//! ```
196//!
197//! ## Traits
198//!
199//! The `sigma_enum` macro also generates a conversion trait for each enum with
200//! methods for constructing values of the enum of a known variant and
201//! extracting a value of a known type from the enum. Helper methods on the enum
202//! for extraction are also generated.
203//!
204//! ```rust
205//! # use sigma_enum::sigma_enum;
206//! struct Bytes<const N: usize>([u8; N]);
207//!
208//! #[sigma_enum(generic(Bytes<usize>))]
209//! enum BytesEnum {
210//!     #[sigma_enum(expand(N = 0..3))]
211//!     __(Bytes<N>),
212//! }
213//!
214//! let bytes_enum = Bytes([0x41; 2]).into_bytes_enum(); // uses IntoBytesEnum trait
215//! let bytes: &Bytes<2> = Bytes::<2>::try_from_bytes_enum(&bytes_enum).unwrap(); // uses IntoBytesEnum trait
216//! let bytes: Bytes<2> = bytes_enum.extract_owned().unwrap();
217//! ```
218//!
219//! `From`, `Into`, `TryFrom`, and `TryInto` will also be implemented between
220//! the enum and all types it contains as variants.
221//!
222//! ## Public API generation
223//!
224//! Marking the enum `pub` will export the generated macros and traits as
225//! part of the public API.
226//!
227//! <div class="warning">
228//!
229//! For enums whose macros you intend to use in
230//! another module of the crate, you must add the `path` attribute that contains
231//! the absolute path to the module.
232//!
233//! For public enums, you may not use the generated macros in the same crate
234//! they were defined in. This is a limitation of Rust's macro system
235//! (cf. [issue #52234](https://github.com/rust-lang/rust/pull/52234)).
236//! In this case, adding the `path` attribute will generate two sets of macros:
237//! one that is exported at the crate root, and another usable in the definition
238//! crate whose names are suffixed by `_crate`.
239//!
240//! ```rust
241//! pub mod inner {
242//!     # use sigma_enum::sigma_enum;
243//!     pub struct Foo;
244//!
245//!     #[sigma_enum(path = crate::inner)]
246//!     pub enum FooEnum {
247//!         __(Foo),
248//!     }
249//! }
250//!
251//! # fn main(){
252//! inner::foo_enum_construct_crate!(Foo(inner::Foo));
253//! // foo_enum_construct!(Foo(inner::Foo)); // cannot refer to this macro
254//! # }
255//! ```
256//!
257//! </div>
258//!
259//! ### Renaming generated items
260//!
261//! Generated items can be renamed and docstrings can be provided with the
262//! following attributes:
263//!
264//! | Item                               | Attribute name          |
265//! | ---------------------------------- | ----------------------- |
266//! | Construction macro                 | `macro_construct`       |
267//! | Match macro                        | `macro_match`           |
268//! | Enum trait                         | `into_trait`            |
269//! | Enum trait construction method     | `into_method`           |
270//! | Enum trait extraction method       | `try_from_method`       |
271//! | Enum trait owned extraction method | `try_from_owned_method` |
272//! | Enum trait mut extraction method   | `try_from_mut_method`   |
273//! | Enum extraction method             | `extract_method`        |
274//! | Enum owned extraction method       | `extract_owned_method`  |
275//! | Enum mut extraction method         | `extract_mut_method`    |
276//! | Enum trait error                   | `try_from_error`        |
277//!
278//! ```rust
279//! # use sigma_enum::sigma_enum;
280//! #[sigma_enum(
281//!     macro_construct(name = make_numeric, docs = "Make a numeric value."),
282//!     macro_match(name = match_numeric, docs = "Match a numeric value.")
283//! )]
284//! enum Numeric {
285//!     __(i32),
286//!     __(i64),
287//! }
288//! ```
289//!
290//! ## Additional information
291//!
292//! Derive macros and other item attributes will work when placed below the
293//! `sigma_enum` macro. Variant attributes will be copied to every instance of
294//! the variant if expanded.
295
296/// The macro.
297///
298/// See the crate root for detailed documentation.
299pub use sigma_enum_macros::sigma_enum;
300
301#[cfg(test)]
302mod tests {
303    use super::sigma_enum;
304
305    pub struct A;
306    pub struct B;
307
308    #[sigma_enum(path = crate::tests)]
309    pub enum AbEnum {
310        __(A),
311        __(B),
312    }
313
314    struct FooType<T>(T);
315    impl FooType<A> {
316        fn is_a(&self) -> bool {
317            true
318        }
319    }
320    impl FooType<B> {
321        fn is_a(&self) -> bool {
322            false
323        }
324    }
325
326    #[sigma_enum(alias(Foo = FooType))]
327    enum FooEnum {
328        __(Foo<A>),
329        __(Foo<B>),
330    }
331
332    #[derive(Debug, Clone, Copy)]
333    struct Mu<const N: usize>([(); N]);
334    #[derive(Debug, Clone, Copy)]
335    struct Nu<M>(M);
336
337    #[sigma_enum(
338        generic(Mu<usize>),
339        macro_match(name = nu_match, docs =
340"A macro to match Nu."
341        ),
342    )]
343    #[derive(Debug, Clone, Copy)]
344    #[non_exhaustive]
345    enum NuEnum {
346        #[sigma_enum(expand(N = 0..=3))]
347        __(Nu<Mu<N>>),
348        NuMu5(Nu<Mu<5>>),
349        #[sigma_enum(expand(N = [7..9, 11]), rename = "NuMu{N}_Big", docs = "Nu Mu {N}. Big.")]
350        __(Nu<Mu<N>>),
351    }
352
353    #[sigma_enum]
354    enum NuEnumNoGen {
355        #[sigma_enum(expand(N = 0..=3))]
356        __(Nu<Mu<N>>),
357        __(Nu<Mu<5>>),
358        #[sigma_enum(expand(N = [7..9, 11]))]
359        __(Nu<Mu<N>>),
360    }
361
362    #[sigma_enum]
363    enum EmptyEnum {}
364
365    // test if those in inner modules compile outside
366    pub mod inner {
367        #![no_implicit_prelude]
368        use crate::sigma_enum;
369        use crate::tests::A;
370        use crate::tests::B;
371
372        #[sigma_enum(path = crate::tests::inner)]
373        pub enum AbEnumI {
374            __(A),
375            __(B),
376        }
377    }
378
379    #[test]
380    fn match_ab_enum() {
381        assert_eq!(
382            ab_enum_match_crate!(match ab_enum_construct_crate!(A(A)).unwrap() {
383                A(_ab) => 1,
384                B(_ab) => 2,
385            }),
386            1
387        );
388
389        assert_eq!(
390            ab_enum_match_crate!(match ab_enum_construct_crate!(A(A)).unwrap() {
391                B(_ab) => 1,
392                _ab => 2,
393            }),
394            2
395        );
396    }
397
398    #[test]
399    fn match_ab_enum_i() {
400        assert_eq!(
401            inner::ab_enum_i_match_crate!(match inner::AbEnumI::A(A) {
402                A(_ab) => 1,
403                B(_ab) => 2,
404            }),
405            1
406        );
407
408        assert_eq!(
409            inner::ab_enum_i_match_crate!(match inner::AbEnumI::A(A) {
410                B(_ab) => 1,
411                _ab => 2,
412            }),
413            2
414        );
415    }
416
417    #[test]
418    fn match_foo_enum() {
419        assert_eq!(
420            foo_enum_match!(match FooEnum::Foo_B(FooType(B)) {
421                Foo::<A>(_foo) => 1,
422                Foo::<B>(_foo) => 2,
423            }),
424            2
425        );
426
427        assert!(foo_enum_match!(match FooEnum::Foo_A(FooType(A)) {
428            Foo::<?T>(foo) => foo.is_a(),
429            Foo::<A>(_foo) => false, // intentionally does not match
430        }),);
431
432        assert!(foo_enum_match!(match FooEnum::Foo_A(FooType(A)) {
433            foo => foo.is_a(),
434        }),);
435    }
436
437    #[test]
438    fn match_nu_enum() {
439        // let numu2 = NuEnum::Nu_Mu_2(Nu(Mu([(); 2])));
440        let n = 2;
441        let numu2 = nu_enum_construct!(Nu::<Mu<?n>>({
442            let arr = [(); n];
443            Nu(Mu(arr))
444        }))
445        .unwrap();
446
447        assert_eq!(
448            nu_match!(match numu2 {
449                Nu::<Mu<0>>(_nu) => 9,
450                Nu::<Mu<?N>>(_nu) => N,
451                _nu => 99,
452            }),
453            2
454        );
455
456        // check const in there
457        assert_eq!(
458            nu_match!(match numu2 {
459                Nu::<Mu<?N>>(_nu) => {
460                    let _mu_arr = [(); N];
461                    N
462                }
463            }),
464            2
465        );
466
467        // make sure types don't blow up the let
468        assert_eq!(
469            nu_match!(match numu2 {
470                Nu::<?T>(_nu) => 2,
471            }),
472            2
473        );
474    }
475
476    #[test]
477    fn match_nu_enum_no_gen() {
478        assert_eq!(
479            nu_enum_no_gen_match!(match (NuEnumNoGen::Nu_Mu_2(Nu(Mu([(), ()])))) {
480                Nu::<Mu<0>>(_nu) => 9,
481                Nu::<Mu<?N>>(_nu) => N,
482            }),
483            2
484        );
485    }
486}