1 use crate::utils::die;
2 use proc_macro2::Span;
3 use syn::{
4     parse::{Parse, ParseStream},
5     Error, Result,
6 };
7 
8 mod 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))]
16 pub(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))]
22 pub(crate) enum AttributeItem {
23     ErrorType(ErrorTypeAttribute),
24 }
25 
26 impl Parse for Attributes {
parse(input: ParseStream<'_>) -> Result<Self>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 
49 impl Parse for AttributeItem {
parse(input: ParseStream<'_>) -> Result<Self>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))]
63 pub(crate) struct ErrorTypeAttribute {
64     pub(crate) name: ErrorTypeNameAttribute,
65     pub(crate) constructor: ErrorTypeConstructorAttribute,
66 
67     span: Span,
68 }
69 
70 impl Parse for ErrorTypeAttribute {
parse(input: ParseStream) -> Result<Self>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
121 pub(crate) enum ErrorTypeAttributeNamedArgument {
122     Name(ErrorTypeNameAttribute),
123     Constructor(ErrorTypeConstructorAttribute),
124 }
125 
126 impl Parse for ErrorTypeAttributeNamedArgument {
parse(input: ParseStream<'_>) -> Result<Self>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))]
142 pub(crate) struct ErrorTypeNameAttribute {
143     pub(crate) path: syn::Path,
144 }
145 
146 impl Parse for ErrorTypeNameAttribute {
parse(input: ParseStream) -> Result<Self>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))]
158 pub(crate) struct ErrorTypeConstructorAttribute {
159     pub(crate) path: syn::Path,
160 }
161 
162 impl Parse for ErrorTypeConstructorAttribute {
parse(input: ParseStream) -> Result<Self>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)]
172 mod test {
173     use crate::enum_attributes::Attributes;
174     use quote::ToTokens;
175     use syn::{parse_quote, Path};
176 
177     #[test]
parse_num_enum_attr()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]
parse_num_enum_attr_swapped_order()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]
missing_constructor()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]
missing_name()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]
missing_both()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]
extra_attr()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]
multiple_names()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]
multiple_constructors()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 }
275