typeful/
lib.rs

1//! #### A collection of helper derive macros for type patterns
2
3#![doc(
4    html_logo_url = "https://ardaku.github.io/mm/logo.svg",
5    html_favicon_url = "https://ardaku.github.io/mm/icon.svg",
6    html_root_url = "https://docs.rs/typeful"
7)]
8#![forbid(unsafe_code, missing_docs)]
9#![warn(
10    anonymous_parameters,
11    missing_copy_implementations,
12    missing_debug_implementations,
13    nonstandard_style,
14    rust_2018_idioms,
15    single_use_lifetimes,
16    trivial_casts,
17    trivial_numeric_casts,
18    unreachable_pub,
19    unused_extern_crates,
20    unused_qualifications,
21    variant_size_differences
22)]
23
24mod common;
25
26use proc_macro::TokenStream;
27use quote::quote;
28use syn::{
29    parse::{Parse, ParseStream},
30    punctuated::Punctuated,
31    Data, Error, Ident, Meta, Result, Token,
32};
33
34struct AttrParams {
35    attrs: Punctuated<Ident, Token![,]>,
36}
37
38impl Parse for AttrParams {
39    fn parse(input: ParseStream<'_>) -> Result<Self> {
40        Ok(Self {
41            attrs: Punctuated::parse_terminated(input)?,
42        })
43    }
44}
45
46fn impl_enum_functions(
47    ast: syn::DeriveInput,
48) -> Result<proc_macro2::TokenStream> {
49    let mut token_stream = proc_macro2::TokenStream::new();
50    let name = ast.ident;
51    let attrs = ast.attrs;
52    let data = ast.data;
53
54    for attr in attrs {
55        let Meta::List(list) = attr.meta else {
56            continue;
57        };
58        let path = &list.path;
59
60        if path.leading_colon.is_some() {
61            return Err(Error::new(
62                list.delimiter.span().join(),
63                "unexpected leading double colon",
64            ));
65        }
66
67        if path.segments.len() != 1 {
68            return Err(Error::new(
69                list.delimiter.span().join(),
70                "unexpected double colon in path",
71            ));
72        }
73
74        let path = path.segments.first().unwrap();
75
76        if !path.arguments.is_none() {
77            return Err(Error::new(
78                list.delimiter.span().join(),
79                "unexpected path arguments",
80            ));
81        }
82
83        if path.ident != "enum_functions" {
84            continue;
85        }
86
87        let Data::Enum(ref data_enum) = data else {
88            return Err(Error::new(path.ident.span(), "expected enum"));
89        };
90        let params: AttrParams = syn::parse2(list.tokens)?;
91        let variants: Punctuated<syn::Path, Token![,]> = data_enum
92            .variants
93            .iter()
94            .map(|v| -> syn::Path {
95                let ident = &v.ident;
96
97                syn::parse_quote!(Self::#ident)
98            })
99            .collect();
100        let variant_count =
101            proc_macro2::Literal::usize_suffixed(variants.len());
102        let mut has_variant_array = false;
103        let mut has_variant_count = false;
104
105        for attr in params.attrs {
106            match attr {
107                a if a == "variant_array" => {
108                    if has_variant_array {
109                        return Err(Error::new(
110                            a.span(),
111                            "duplicated attribute",
112                        ));
113                    }
114
115                    has_variant_array = true;
116                    token_stream.extend(quote! {
117                        impl #name {
118                            const fn variant_array<const N: usize>() -> [Self; N] {
119                                if N > #variant_count {
120                                    panic!("requested variant array is too large")
121                                }
122
123                                let full_array = [#variants];
124                                let mut array = [full_array[0]; N];
125                                let mut i = 1;
126
127                                while i < N {
128                                    array[i] = full_array[i];
129                                    i += 1;
130                                }
131
132                                array
133                            }
134                        }
135                    });
136                }
137                a if a == "variant_count" => {
138                    if has_variant_count {
139                        return Err(Error::new(
140                            a.span(),
141                            "duplicated attribute",
142                        ));
143                    }
144
145                    has_variant_count = true;
146                    token_stream.extend(quote! {
147                        impl #name {
148                            const fn variant_count() -> usize {
149                                #variant_count
150                            }
151                        }
152                    });
153                }
154                ident => {
155                    return Err(Error::new(ident.span(), "unknown attribute"))
156                }
157            }
158        }
159    }
160
161    Ok(token_stream)
162}
163
164/// Derive a set of enum-related methods not tied to a trait.
165#[proc_macro_derive(EnumFunctions, attributes(enum_functions))]
166pub fn enum_functions_derive(input: TokenStream) -> TokenStream {
167    let ast = syn::parse(input).unwrap();
168
169    common::unwrap(impl_enum_functions(ast))
170}