derive_syn_parse/
variants.rs

1//! Handling for generating a `Parse` implementation from `enum` variants
2
3use crate::fields::generate_fn_body;
4use proc_macro2::{Span, TokenStream};
5use quote::{quote, quote_spanned};
6use syn::parse::{Parse, ParseStream};
7use syn::spanned::Spanned;
8use syn::{Attribute, Expr, Ident, LitStr, Result, Token, Variant};
9
10pub(crate) fn generate_impl(
11    variants: impl ExactSizeIterator<Item = Variant>,
12) -> Result<TokenStream> {
13    // generate each `peek` and corresponding inner implementation
14
15    if variants.len() == 0 {
16        return Err(syn::Error::new(
17            Span::call_site(),
18            "cannot derive `Parse` for an empty `enum`",
19        ));
20    }
21
22    let input_source = crate::parse_input();
23
24    let mut names = Vec::new();
25    let conditional_return_if_peek_success = variants
26        .into_iter()
27        .map(|var| {
28            let (name, if_condition) = impl_for_variant(&input_source, var)?;
29            names.push(name);
30            Ok(if_condition)
31        })
32        .collect::<Result<Vec<_>>>()?;
33
34    let error_msg = implemented_error_msg(names);
35
36    Ok(quote! {
37        #( #conditional_return_if_peek_success )*
38
39        Err(#input_source.error(#error_msg))
40    })
41}
42
43fn implemented_error_msg(names: Vec<LitStr>) -> String {
44    let one_of = match names.len() {
45        1 => "",
46        2 => "either ",
47        _ => "one of ",
48    };
49
50    let name_list = match names.len() {
51        0 => unreachable!(),
52        1 => names[0].value(),
53        2 => format!("{} or {}", names[0].value(), names[1].value()),
54        _ => {
55            let middle = names[1..names.len() - 1]
56                .iter()
57                .map(|name| format!(", {}", name.value()))
58                .collect::<String>();
59            format!(
60                "{}{}, or {}",
61                names[0].value(),
62                middle,
63                names.last().unwrap().value()
64            )
65        }
66    };
67
68    format!("expected {}{}", one_of, name_list)
69}
70
71enum VariantAttr {
72    Peek(PeekInfo),
73    PeekWith(PeekInfo),
74}
75
76mod kwd {
77    syn::custom_keyword!(name);
78}
79
80struct PeekInfo {
81    expr: Expr,
82    _comma: Token![,],
83    _name_token: kwd::name,
84    _eq: Token![=],
85    name: LitStr,
86}
87
88// If successful, the first element in the tuple is the name to use to refer to the variant in
89// error messages. The second element is an `if` expression that looks something like:
90//
91//   if $input_source.peek($peek_value) {
92//       Ok(Self::$variant_name {
93//          $( $field: $input_source.parse()?, )*
94//       })
95//   }
96fn impl_for_variant(input_source: &Ident, variant: Variant) -> Result<(LitStr, TokenStream)> {
97    use VariantAttr::{Peek, PeekWith};
98
99    let variant_span = variant.span();
100
101    let diagnositc_name: LitStr;
102    let peek_expr = match extract_single_attr(variant_span, variant.attrs)? {
103        Peek(PeekInfo { expr, name, .. }) => {
104            diagnositc_name = name;
105            quote_spanned! {
106                expr.span()=>
107                #input_source.peek(#expr)
108            }
109        }
110        PeekWith(PeekInfo { expr, name, .. }) => {
111            diagnositc_name = name;
112            quote_spanned! {
113                expr.span()=>
114                (#expr)(#input_source)
115            }
116        }
117    };
118
119    let ident = variant.ident;
120    let variant_path = quote!( Self::#ident );
121    let parse_implementation = generate_fn_body(&variant_path, variant.fields, true)?;
122
123    let output = quote! {
124        if #peek_expr {
125            #parse_implementation;
126        }
127    };
128
129    Ok((diagnositc_name, output))
130}
131
132fn extract_single_attr(variant_span: Span, attrs: Vec<Attribute>) -> Result<VariantAttr> {
133    let mut attrs: Vec<_> = attrs
134        .into_iter()
135        .filter_map(try_as_variant_attr)
136        .collect::<Result<_>>()?;
137
138    match attrs.len() {
139        0 => Err(syn::Error::new(
140            variant_span,
141            "enum variants must have `#[peek(..)]` or `#[peek_with(..)]` to derive `Parse`",
142        )),
143        1 => Ok(attrs.remove(0)),
144        _ => Err(syn::Error::new(
145            variant_span,
146            "more than one peeking attribute is disallowed; please use `#[peek_with(..)]` for a custom function",
147        )),
148    }
149}
150
151fn try_as_variant_attr(attr: Attribute) -> Option<Result<VariantAttr>> {
152    let name = attr.path().get_ident()?.to_string();
153
154    match name.as_str() {
155        "peek" => Some(attr.parse_args().map(VariantAttr::Peek)),
156        "peek_with" => Some(attr.parse_args().map(VariantAttr::PeekWith)),
157        _ => None,
158    }
159}
160
161////////////////////////////////////////////
162// Boilerplate `Parse` implementations 🙃 //
163////////////////////////////////////////////
164
165impl Parse for PeekInfo {
166    fn parse(input: ParseStream) -> Result<Self> {
167        Ok(PeekInfo {
168            expr: input.parse()?,
169            _comma: input.parse()?,
170            _name_token: input.parse()?,
171            _eq: input.parse()?,
172            name: input.parse()?,
173        })
174    }
175}