derive_arbitrary/
container_attributes.rs

1use crate::ARBITRARY_ATTRIBUTE_NAME;
2use syn::{
3    parse::Error, punctuated::Punctuated, DeriveInput, Expr, ExprLit, Lit, Meta, MetaNameValue,
4    Token, TypeParam,
5};
6
7pub 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
24impl ContainerAttributes {
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}