parse_variants_derive/
lib.rs

1use proc_macro::TokenStream;
2
3use quote::quote;
4use syn::spanned::Spanned;
5use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields};
6
7/*
8 See the documentation in the parse-variants crate on how to use the macro.
9 Here is a quick example for how the generated code looks.
10 We apply the macro to this enum:
11
12     #[derive(crate::Parse)]
13     enum EnumWithMixedVariants {
14        TwoExpressionsSeparatedByKeyword {
15            first: syn::Expr,
16            _the_dude: keywords::lebowski,
17            second: syn::Expr,
18        },
19        IdentifierPlusPlus(Ident, syn::token::Add,syn::token::Add),
20     }
21
22 Then the generate syn::parse::Parse implementation looks like this:
23
24    impl ::syn::parse::Parse for EnumWithMixedVariants {
25        fn parse(input: &::syn::parse::ParseBuffer) -> ::std::result::Result<Self, ::syn::Error> {
26            use ::syn::parse::discouraged::Speculative; //needed for input.advance_to(...)
27            // 1) fork input for first variant and try if we can parse it
28            let fork = input.fork();
29            if let Ok(variant) = (|| {   // here we use a closure to return a result or error without returning the error directly from our parse function
30                // this is how parsing named fields looks like
31                Ok(EnumWithMixedVariants::TwoExpressionsSeparatedByKeyword {
32                    first: fork.parse()?,
33                    _the_dude: fork.parse()?,
34                    second: fork.parse()?,
35                }) as ::std::result::Result<EnumWithMixedVariants, ::syn::Error>
36            })() {
37                // if we can parse the variant, advance the parsebuffer and return immediately
38                input.advance_to(&fork);
39                return Ok(variant);
40            }
41            // 2) fork the second variant
42            let fork = input.fork();
43            if let Ok(variant) = (|| {   // same trick with the closure as above to catch the error returns
44                // this is how parsing named fields looks like
45                Ok(EnumWithMixedVariants::IdentifierPlusPlus(
46                    fork.parse()?,
47                    fork.parse()?,
48                    fork.parse()?,
49                )) as ::std::result::Result<EnumWithMixedVariants, ::syn::Error>
50            })() {
51                input.advance_to(&fork);
52                return Ok(variant);
53            }
54            // if no variants can be parsed, return an error
55            Err(syn::Error::new(
56                input.span(),
57                ::std::format! {
58                    "parse error: tokens cannot be parsed as any variant of {}",
59                    ::std::stringify! { EnumWithMixedVariants }
60                },
61            ))
62        }
63    }
64*/
65
66#[proc_macro_derive(Parse)]
67pub fn derive_parse_variants(input: TokenStream) -> TokenStream {
68    let input = parse_macro_input!(input as DeriveInput);
69    let enum_ident = &input.ident;
70    let data_enum = match get_data_enum(&input) {
71        Ok(data_enum) => data_enum,
72        Err(error) => {
73            return error;
74        }
75    };
76
77    // here we generate the code that tries to parse the actual variants by repeatedly forking
78    // the input parse buffer and then trying to parse the input as the contents of the respective
79    // variant.
80    let mut try_parse_variants = proc_macro2::TokenStream::new();
81
82    for variant in data_enum.variants.iter() {
83        let variant_name = &variant.ident;
84        let try_parse_variant = match variant.fields {
85            Fields::Named(ref fields_named) => {
86                let fields: Vec<_> = fields_named
87                    .named
88                    .iter()
89                    .map(|field| field.ident.as_ref().unwrap())
90                    .collect();
91                // generated code looks e.g. like this
92                // Ok(MyEnum::StructLikeVariant{field1 : fork.parse()?, field2 : fork.parse()?})
93                quote! {
94                        Ok(#enum_ident::#variant_name {#(#fields : fork.parse()?),*})
95                }
96            }
97            Fields::Unnamed(ref fields_unnamed) => {
98                let fork_parse_questionmark = quote! {fork.parse()?};
99                let repeated_input_parsing =
100                    std::iter::repeat(fork_parse_questionmark).take(fields_unnamed.unnamed.len());
101                // code looks e.g. like this
102                // Ok(MyEnum::TupleLikeVariant(fork.parse()?,fork.parse()?))
103                // where fork.parse()? is repeated for each field of the tuple like variant
104                quote! {
105                    Ok(#enum_ident::#variant_name (#(#repeated_input_parsing),*))
106                }
107            }
108            Fields::Unit => {
109                // unit like variants (i.e. variants with no fields)
110                // cannot be parsed and will return a compile error
111                return syn::Error::new(
112                    variant.ident.span(),
113                    "illegal unit variant: enumeration may not have variants without fields",
114                )
115                .to_compile_error()
116                .into();
117            }
118        };
119
120        try_parse_variants.extend(quote! {
121            let fork = input.fork();
122            if let Ok(variant) = (||{#try_parse_variant as ::std::result::Result<#enum_ident,::syn::Error>})() { //TODO: document this: less verbose variant than before. So that the error type can be caught at the boundary of this closure and does not propagate outside of the parse function
123                input.advance_to(&fork);
124                return Ok(variant);
125            }
126        })
127    }
128
129    // the implementation of the derive trait
130    let parse_impl_tokens = quote! {
131        impl ::syn::parse::Parse for #enum_ident {
132            fn parse(input : & ::syn::parse::ParseBuffer) -> ::std::result::Result<Self, ::syn::Error> {
133                // we have to use this for the advante_to method in the parsing body
134                use ::syn::parse::discouraged::Speculative;
135                // parsing the variants
136                #try_parse_variants
137                // if none of the variants can be parsed, return an error
138                Err(syn::Error::new(input.span(),::std::format!{"parse error: tokens cannot be parsed as any variant of {}", ::std::stringify!{#enum_ident}}))
139            }
140        }
141    };
142
143    //println!("IMPLEMENTATION = \n{}", parse_impl_tokens.to_string());
144    //panic!();
145
146    parse_impl_tokens.into()
147}
148
149// helper function to return the DataEnum of the derive input.
150// # Returns
151// the DataEnum field, if the derive input is an enum and if the enum has at least one variant.
152// If the enum has no variants or the derive input is not an enum, a descriptive error is returned. The
153// error is returned as TokenStream and can be passed on directly.
154fn get_data_enum(input: &DeriveInput) -> Result<&DataEnum, TokenStream> {
155    match input.data {
156        Data::Enum(ref data_enum) => {
157            if !data_enum.variants.is_empty() {
158                Ok(data_enum)
159            } else {
160                Err(syn::Error::new(
161                    input
162                        .span()
163                        .join(input.ident.span())
164                        .unwrap_or_else(|| input.ident.span()),
165                    "no variants: enumeration must have at least one variant",
166                )
167                .to_compile_error()
168                .into())
169            }
170        }
171        Data::Union(_) | Data::Struct(_) => Err(syn::Error::new(
172            input.span(),
173            "expected enum: parsing variants only works with enumerations",
174        )
175        .to_compile_error()
176        .into()),
177    }
178}