1 use crate::ARBITRARY_ATTRIBUTE_NAME;
2 use syn::{
3     parse::Error, punctuated::Punctuated, DeriveInput, Expr, ExprLit, Lit, Meta, MetaNameValue,
4     Token, TypeParam,
5 };
6 
7 pub struct ContainerAttributes {
8     /// Specify type bounds to be applied to the derived `Arbitrary` implementation instead of the
9     /// default inferred bounds.
10     ///
11     /// ```ignore
12     /// #[arbitrary(bound = "T: Default, U: Debug")]
13     /// ```
14     ///
15     /// Multiple attributes will be combined as long as they don't conflict, e.g.
16     ///
17     /// ```ignore
18     /// #[arbitrary(bound = "T: Default")]
19     /// #[arbitrary(bound = "U: Default")]
20     /// ```
21     pub bounds: Option<Vec<Punctuated<TypeParam, Token![,]>>>,
22 }
23 
24 impl ContainerAttributes {
from_derive_input(derive_input: &DeriveInput) -> Result<Self, Error>25     pub fn from_derive_input(derive_input: &DeriveInput) -> Result<Self, Error> {
26         let mut bounds = None;
27 
28         for attr in &derive_input.attrs {
29             if !attr.path().is_ident(ARBITRARY_ATTRIBUTE_NAME) {
30                 continue;
31             }
32 
33             let meta_list = match attr.meta {
34                 Meta::List(ref l) => l,
35                 _ => {
36                     return Err(Error::new_spanned(
37                         attr,
38                         format!(
39                             "invalid `{}` attribute. expected list",
40                             ARBITRARY_ATTRIBUTE_NAME
41                         ),
42                     ))
43                 }
44             };
45 
46             for nested_meta in
47                 meta_list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?
48             {
49                 match nested_meta {
50                     Meta::NameValue(MetaNameValue {
51                         path,
52                         value:
53                             Expr::Lit(ExprLit {
54                                 lit: Lit::Str(bound_str_lit),
55                                 ..
56                             }),
57                         ..
58                     }) if path.is_ident("bound") => {
59                         bounds
60                             .get_or_insert_with(Vec::new)
61                             .push(bound_str_lit.parse_with(Punctuated::parse_terminated)?);
62                     }
63                     _ => {
64                         return Err(Error::new_spanned(
65                             attr,
66                             format!(
67                                 "invalid `{}` attribute. expected `bound = \"..\"`",
68                                 ARBITRARY_ATTRIBUTE_NAME,
69                             ),
70                         ))
71                     }
72                 }
73             }
74         }
75 
76         Ok(Self { bounds })
77     }
78 }
79