typed_builder_macro/
mutator.rs

1use std::collections::HashSet;
2
3use proc_macro2::Ident;
4use syn::{
5    parse::{Parse, ParseStream},
6    parse_quote,
7    punctuated::Punctuated,
8    spanned::Spanned,
9    Error, Expr, FnArg, ItemFn, PatIdent, ReturnType, Signature, Token, Type,
10};
11
12use crate::util::{pat_to_ident, ApplyMeta, AttrArg};
13
14#[derive(Debug, Clone)]
15pub struct Mutator {
16    pub fun: ItemFn,
17    pub required_fields: HashSet<Ident>,
18}
19
20#[derive(Default)]
21struct MutatorAttribute {
22    requires: HashSet<Ident>,
23}
24
25impl ApplyMeta for MutatorAttribute {
26    fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> {
27        if expr.name() != "requires" {
28            return Err(Error::new_spanned(expr.name(), "Only `requires` is supported"));
29        }
30
31        match expr.key_value()?.parse_value()? {
32            Expr::Array(syn::ExprArray { elems, .. }) => self.requires.extend(
33                elems
34                    .into_iter()
35                    .map(|expr| match expr {
36                        Expr::Path(path) if path.path.get_ident().is_some() => {
37                            Ok(path.path.get_ident().cloned().expect("should be ident"))
38                        }
39                        expr => Err(Error::new_spanned(expr, "Expected field name")),
40                    })
41                    .collect::<Result<Vec<_>, _>>()?,
42            ),
43            expr => {
44                return Err(Error::new_spanned(
45                    expr,
46                    "Only list of field names [field1, field2, …] supported",
47                ))
48            }
49        }
50        Ok(())
51    }
52}
53
54impl Parse for Mutator {
55    fn parse(input: ParseStream) -> syn::Result<Self> {
56        let mut fun: ItemFn = input.parse()?;
57
58        let mut attribute = MutatorAttribute::default();
59
60        let mut i = 0;
61        while i < fun.attrs.len() {
62            let attr = &fun.attrs[i];
63            if attr.path().is_ident("mutator") {
64                attribute.apply_attr(attr)?;
65                fun.attrs.remove(i);
66            } else {
67                i += 1;
68            }
69        }
70
71        // Ensure `&mut self` receiver
72        if let Some(FnArg::Receiver(receiver)) = fun.sig.inputs.first_mut() {
73            *receiver = parse_quote!(&mut self);
74        } else {
75            // Error either on first argument or `()`
76            return Err(syn::Error::new(
77                fun.sig
78                    .inputs
79                    .first()
80                    .map(Spanned::span)
81                    .unwrap_or(fun.sig.paren_token.span.span()),
82                "mutator needs to take a reference to `self`",
83            ));
84        };
85
86        Ok(Self {
87            fun,
88            required_fields: attribute.requires,
89        })
90    }
91}
92
93impl Mutator {
94    /// Signature for Builder::<mutator> function
95    pub fn outer_sig(&self, output: Type) -> Signature {
96        let mut sig = self.fun.sig.clone();
97        sig.output = ReturnType::Type(Default::default(), output.into());
98
99        sig.inputs = sig
100            .inputs
101            .into_iter()
102            .enumerate()
103            .map(|(i, input)| match input {
104                FnArg::Receiver(_) => parse_quote!(self),
105                FnArg::Typed(mut input) => {
106                    input.pat = Box::new(
107                        PatIdent {
108                            attrs: Vec::new(),
109                            by_ref: None,
110                            mutability: None,
111                            ident: pat_to_ident(i, &input.pat),
112                            subpat: None,
113                        }
114                        .into(),
115                    );
116                    FnArg::Typed(input)
117                }
118            })
119            .collect();
120        sig
121    }
122
123    /// Arguments to call inner mutator function
124    pub fn arguments(&self) -> Punctuated<Ident, Token![,]> {
125        self.fun
126            .sig
127            .inputs
128            .iter()
129            .enumerate()
130            .filter_map(|(i, input)| match &input {
131                FnArg::Receiver(_) => None,
132                FnArg::Typed(input) => Some(pat_to_ident(i, &input.pat)),
133            })
134            .collect()
135    }
136}