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}