num_enum_derive/
enum_attributes.rs

1use crate::utils::die;
2use proc_macro2::Span;
3use syn::{
4    parse::{Parse, ParseStream},
5    Error, Result,
6};
7
8mod kw {
9    syn::custom_keyword!(constructor);
10    syn::custom_keyword!(error_type);
11    syn::custom_keyword!(name);
12}
13
14// Example: error_type(name = Foo, constructor = Foo::new)
15#[cfg_attr(test, derive(Debug))]
16pub(crate) struct Attributes {
17    pub(crate) error_type: Option<ErrorTypeAttribute>,
18}
19
20// Example: error_type(name = Foo, constructor = Foo::new)
21#[cfg_attr(test, derive(Debug))]
22pub(crate) enum AttributeItem {
23    ErrorType(ErrorTypeAttribute),
24}
25
26impl Parse for Attributes {
27    fn parse(input: ParseStream<'_>) -> Result<Self> {
28        let attribute_items = input.parse_terminated(AttributeItem::parse, syn::Token![,])?;
29        let mut maybe_error_type = None;
30        for attribute_item in &attribute_items {
31            match attribute_item {
32                AttributeItem::ErrorType(error_type) => {
33                    if maybe_error_type.is_some() {
34                        return Err(Error::new(
35                            error_type.span,
36                            "num_enum attribute must have at most one error_type",
37                        ));
38                    }
39                    maybe_error_type = Some(error_type.clone());
40                }
41            }
42        }
43        Ok(Self {
44            error_type: maybe_error_type,
45        })
46    }
47}
48
49impl Parse for AttributeItem {
50    fn parse(input: ParseStream<'_>) -> Result<Self> {
51        let lookahead = input.lookahead1();
52        if lookahead.peek(kw::error_type) {
53            input.parse().map(Self::ErrorType)
54        } else {
55            Err(lookahead.error())
56        }
57    }
58}
59
60// Example: error_type(name = Foo, constructor = Foo::new)
61#[derive(Clone)]
62#[cfg_attr(test, derive(Debug))]
63pub(crate) struct ErrorTypeAttribute {
64    pub(crate) name: ErrorTypeNameAttribute,
65    pub(crate) constructor: ErrorTypeConstructorAttribute,
66
67    span: Span,
68}
69
70impl Parse for ErrorTypeAttribute {
71    fn parse(input: ParseStream) -> Result<Self> {
72        let keyword: kw::error_type = input.parse()?;
73        let span = keyword.span;
74        let content;
75        syn::parenthesized!(content in input);
76        let attribute_values =
77            content.parse_terminated(ErrorTypeAttributeNamedArgument::parse, syn::Token![,])?;
78        let mut name = None;
79        let mut constructor = None;
80        for attribute_value in &attribute_values {
81            match attribute_value {
82                ErrorTypeAttributeNamedArgument::Name(name_attr) => {
83                    if name.is_some() {
84                        die!("num_enum error_type attribute must have exactly one `name` value");
85                    }
86                    name = Some(name_attr.clone());
87                }
88                ErrorTypeAttributeNamedArgument::Constructor(constructor_attr) => {
89                    if constructor.is_some() {
90                        die!("num_enum error_type attribute must have exactly one `constructor` value")
91                    }
92                    constructor = Some(constructor_attr.clone());
93                }
94            }
95        }
96        match (name, constructor) {
97            (None, None) => Err(Error::new(
98                span,
99                "num_enum error_type attribute requires `name` and `constructor` values",
100            )),
101            (Some(_), None) => Err(Error::new(
102                span,
103                "num_enum error_type attribute requires `constructor` value",
104            )),
105            (None, Some(_)) => Err(Error::new(
106                span,
107                "num_enum error_type attribute requires `name` value",
108            )),
109            (Some(name), Some(constructor)) => Ok(Self {
110                name,
111                constructor,
112                span,
113            }),
114        }
115    }
116}
117
118// Examples:
119//  * name = Foo
120//  * constructor = Foo::new
121pub(crate) enum ErrorTypeAttributeNamedArgument {
122    Name(ErrorTypeNameAttribute),
123    Constructor(ErrorTypeConstructorAttribute),
124}
125
126impl Parse for ErrorTypeAttributeNamedArgument {
127    fn parse(input: ParseStream<'_>) -> Result<Self> {
128        let lookahead = input.lookahead1();
129        if lookahead.peek(kw::name) {
130            input.parse().map(Self::Name)
131        } else if lookahead.peek(kw::constructor) {
132            input.parse().map(Self::Constructor)
133        } else {
134            Err(lookahead.error())
135        }
136    }
137}
138
139// Example: name = Foo
140#[derive(Clone)]
141#[cfg_attr(test, derive(Debug))]
142pub(crate) struct ErrorTypeNameAttribute {
143    pub(crate) path: syn::Path,
144}
145
146impl Parse for ErrorTypeNameAttribute {
147    fn parse(input: ParseStream) -> Result<Self> {
148        input.parse::<kw::name>()?;
149        input.parse::<syn::Token![=]>()?;
150        let path = input.parse()?;
151        Ok(Self { path })
152    }
153}
154
155// Example: constructor = Foo::new
156#[derive(Clone)]
157#[cfg_attr(test, derive(Debug))]
158pub(crate) struct ErrorTypeConstructorAttribute {
159    pub(crate) path: syn::Path,
160}
161
162impl Parse for ErrorTypeConstructorAttribute {
163    fn parse(input: ParseStream) -> Result<Self> {
164        input.parse::<kw::constructor>()?;
165        input.parse::<syn::Token![=]>()?;
166        let path = input.parse()?;
167        Ok(Self { path })
168    }
169}
170
171#[cfg(test)]
172mod test {
173    use crate::enum_attributes::Attributes;
174    use quote::ToTokens;
175    use syn::{parse_quote, Path};
176
177    #[test]
178    fn parse_num_enum_attr() {
179        let expected_name: Path = parse_quote! { Foo };
180        let expected_constructor: Path = parse_quote! { ::foo::Foo::<u8>::new };
181
182        let attributes: Attributes =
183            syn::parse_str("error_type(name = Foo, constructor = ::foo::Foo::<u8>::new)").unwrap();
184        assert!(attributes.error_type.is_some());
185        let error_type = attributes.error_type.unwrap();
186        assert_eq!(
187            error_type.name.path.to_token_stream().to_string(),
188            expected_name.to_token_stream().to_string()
189        );
190        assert_eq!(
191            error_type.constructor.path.to_token_stream().to_string(),
192            expected_constructor.to_token_stream().to_string()
193        );
194    }
195
196    #[test]
197    fn parse_num_enum_attr_swapped_order() {
198        let expected_name: Path = parse_quote! { Foo };
199        let expected_constructor: Path = parse_quote! { ::foo::Foo::<u8>::new };
200
201        let attributes: Attributes =
202            syn::parse_str("error_type(constructor = ::foo::Foo::<u8>::new, name = Foo)").unwrap();
203        assert!(attributes.error_type.is_some());
204        let error_type = attributes.error_type.unwrap();
205        assert_eq!(
206            error_type.name.path.to_token_stream().to_string(),
207            expected_name.to_token_stream().to_string()
208        );
209        assert_eq!(
210            error_type.constructor.path.to_token_stream().to_string(),
211            expected_constructor.to_token_stream().to_string()
212        );
213    }
214
215    #[test]
216    fn missing_constructor() {
217        let err = syn::parse_str::<Attributes>("error_type(name = Foo)").unwrap_err();
218        assert_eq!(
219            err.to_string(),
220            "num_enum error_type attribute requires `constructor` value"
221        );
222    }
223
224    #[test]
225    fn missing_name() {
226        let err = syn::parse_str::<Attributes>("error_type(constructor = Foo::new)").unwrap_err();
227        assert_eq!(
228            err.to_string(),
229            "num_enum error_type attribute requires `name` value"
230        );
231    }
232
233    #[test]
234    fn missing_both() {
235        let err = syn::parse_str::<Attributes>("error_type()").unwrap_err();
236        assert_eq!(
237            err.to_string(),
238            "num_enum error_type attribute requires `name` and `constructor` values"
239        );
240    }
241
242    #[test]
243    fn extra_attr() {
244        let err = syn::parse_str::<Attributes>(
245            "error_type(name = Foo, constructor = Foo::new, extra = unneeded)",
246        )
247        .unwrap_err();
248        assert_eq!(err.to_string(), "expected `name` or `constructor`");
249    }
250
251    #[test]
252    fn multiple_names() {
253        let err = syn::parse_str::<Attributes>(
254            "error_type(name = Foo, name = Foo, constructor = Foo::new)",
255        )
256        .unwrap_err();
257        assert_eq!(
258            err.to_string(),
259            "num_enum error_type attribute must have exactly one `name` value"
260        );
261    }
262
263    #[test]
264    fn multiple_constructors() {
265        let err = syn::parse_str::<Attributes>(
266            "error_type(name = Foo, constructor = Foo::new, constructor = Foo::new)",
267        )
268        .unwrap_err();
269        assert_eq!(
270            err.to_string(),
271            "num_enum error_type attribute must have exactly one `constructor` value"
272        );
273    }
274}