bpaf_derive/
named_field.rs

1use proc_macro2::{Span, TokenStream};
2use quote::{quote, ToTokens};
3use syn::{
4    parse::ParseStream, parse_quote, spanned::Spanned, token, Attribute, Error, Ident, LitStr,
5    Result, Type, Visibility,
6};
7
8use crate::{
9    attrs::{
10        parse_bpaf_doc_attrs, Consumer, FieldAttrs, HelpPlacement, Name, Post, PostParse,
11        StrictName, TurboFish,
12    },
13    field::{split_type, Shape},
14    help::Help,
15    utils::to_snake_case,
16};
17
18#[derive(Debug, Clone)]
19pub(crate) struct StructField {
20    pub name: Option<Ident>,
21    pub env: Vec<StrictName>,
22    pub naming: Vec<StrictName>,
23    pub cons: Consumer,
24    pub postpr: Vec<Post>,
25    pub help: Option<Help>,
26}
27
28fn derive_consumer(name_present: bool, ty: &Type) -> Result<Consumer> {
29    let span = ty.span();
30    Ok(match split_type(ty) {
31        Shape::Bool => {
32            if name_present {
33                Consumer::Switch { span }
34            } else {
35                let msg = "Refusing to derive a positional item for bool, you can fix this by either adding a short/long name or making it positional explicitly";
36                return Err(Error::new(ty.span(), msg));
37            }
38        }
39        Shape::Unit => {
40            if name_present {
41                Consumer::ReqFlag {
42                    present: parse_quote!(()),
43                    span,
44                }
45            } else {
46                let msg = "Refusing to derive a positional item for (), you can fix this by either adding a short/long name or making it positional explicitly";
47                return Err(Error::new(ty.span(), msg));
48            }
49        }
50        Shape::Optional(t) | Shape::Multiple(t) | Shape::Direct(t) => {
51            let ty = Some(t);
52            let metavar = None;
53            if name_present {
54                Consumer::Argument { metavar, ty, span }
55            } else {
56                Consumer::Positional { metavar, ty, span }
57            }
58        }
59    })
60}
61
62struct MMetavar<'a>(Option<&'a LitStr>);
63impl ToTokens for MMetavar<'_> {
64    fn to_tokens(&self, tokens: &mut TokenStream) {
65        match &self.0 {
66            Some(mv) => mv.to_tokens(tokens),
67            None => quote!("ARG").to_tokens(tokens),
68        }
69    }
70}
71
72impl ToTokens for Consumer {
73    fn to_tokens(&self, tokens: &mut TokenStream) {
74        match self {
75            Consumer::Switch { .. } => quote!(switch()),
76            Consumer::Flag {
77                present, absent, ..
78            } => quote!(flag(#present, #absent)),
79            Consumer::ReqFlag { present, .. } => quote!(req_flag(#present)),
80            Consumer::Any {
81                metavar, ty, check, ..
82            } => match ty {
83                Some(ty) => quote!(::bpaf::any::<#ty, _, _>(#metavar, #check)),
84                None => quote!(::bpaf::any(#metavar, #check)),
85            },
86            Consumer::Argument { metavar, ty, .. } => {
87                let metavar = MMetavar(metavar.as_ref());
88                let tf = ty.as_ref().map(TurboFish);
89                quote!(argument #tf(#metavar))
90            }
91            Consumer::Positional { metavar, ty, .. } => {
92                let metavar = MMetavar(metavar.as_ref());
93                let tf = ty.as_ref().map(TurboFish);
94                quote!(::bpaf::positional #tf(#metavar))
95            }
96            Consumer::External { ident, .. } => {
97                quote!(#ident())
98            }
99            Consumer::Pure { expr, .. } => {
100                quote!(::bpaf::pure(#expr))
101            }
102            Consumer::PureWith { expr, .. } => {
103                quote!(::bpaf::pure_with(#expr))
104            }
105        }
106        .to_tokens(tokens);
107    }
108}
109
110impl ToTokens for StructField {
111    fn to_tokens(&self, tokens: &mut TokenStream) {
112        let StructField {
113            name: _,
114            env,
115            naming,
116            cons,
117            postpr,
118            help,
119        } = self;
120
121        let names = naming.iter().chain(env.iter());
122
123        let prefix = if cons.needs_name() {
124            quote!(::bpaf::)
125        } else {
126            quote!()
127        };
128
129        let help = help.iter();
130
131        match cons.help_placement() {
132            HelpPlacement::AtName => {
133                quote!(#prefix #( #names .)* #(help(#help).)* #cons #(.#postpr)*)
134            }
135            HelpPlacement::AtConsumer => {
136                quote!(#prefix #( #names .)* #cons #(.help(#help))* #(.#postpr)*)
137            }
138            HelpPlacement::NotAvailable => quote!(#prefix #(#names.)* #cons #(.#postpr)*),
139        }
140        .to_tokens(tokens);
141    }
142}
143
144impl StructField {
145    pub fn var_name(&self, ix: usize) -> Ident {
146        let name = &self.name;
147        match name {
148            Some(name) => name.clone(),
149            None => Ident::new(&format!("f{}", ix), Span::call_site()),
150        }
151    }
152
153    pub fn parse_named(input: ParseStream) -> Result<Self> {
154        let attrs = input.call(Attribute::parse_outer)?;
155        let _vis = input.parse::<Visibility>()?;
156        let name = input.parse::<Ident>()?;
157        input.parse::<token::Colon>()?;
158        let ty = input.parse::<Type>()?;
159        Self::make(Some(name), ty, &attrs)
160    }
161
162    pub fn parse_unnamed(input: ParseStream) -> Result<Self> {
163        let attrs = input.call(Attribute::parse_outer)?;
164        let _vis = input.parse::<Visibility>()?;
165        let ty = input.parse::<Type>()?;
166        Self::make(None, ty, &attrs)
167    }
168
169    #[allow(clippy::too_many_lines)]
170    pub(crate) fn make(name: Option<Ident>, ty: Type, attrs: &[Attribute]) -> Result<Self> {
171        let (field_attrs, mut help) = parse_bpaf_doc_attrs::<FieldAttrs>(attrs)?;
172
173        let mut field_attrs = field_attrs.unwrap_or_default();
174
175        if field_attrs.ignore_rustdoc {
176            help = None;
177        }
178
179        let derived_consumer = field_attrs.consumer.is_empty();
180
181        let mut cons = match field_attrs.consumer.pop() {
182            Some(cons) => cons,
183            None => derive_consumer(name.is_some() || !field_attrs.naming.is_empty(), &ty)?,
184        };
185
186        if let Consumer::External { span, ident: None } = &cons {
187            let span = *span;
188            match name.as_ref() {
189                Some(n) => {
190                    let ident = Ident::new(&to_snake_case(&n.to_string()), n.span());
191                    cons = Consumer::External {
192                        span,
193                        ident: Some(ident.into()),
194                    };
195                }
196                None => {
197                    return Err(Error::new(
198                        span,
199                        "Can't derive name for this external, try specifying one",
200                    ))
201                }
202            }
203        }
204
205        let mut env = Vec::new();
206        let mut naming = Vec::new();
207        for attr in field_attrs.naming {
208            if let Name::Env { name, .. } = attr {
209                env.push(StrictName::Env { name });
210            } else {
211                naming.push(StrictName::from_name(attr, &name)?);
212            }
213        }
214
215        match (cons.needs_name(), !naming.is_empty()) {
216            // need name, there are some. just need to resolve optional shorts and longs later
217            (true, true) |
218            // doesn't need a name, none are given. all good
219            (false, false) => {}
220
221            // needs a name, none specified, derive it from `name` field
222            (true, false) => match &name {
223                Some(n) => {
224                    let span = n.span();
225                    if n.to_string().chars().count() == 1 {
226                        let short = Name::Short { name: None, span };
227                        naming.push(StrictName::from_name(short, &name)?);
228                    } else {
229                        let long = Name::Long { name: None, span };
230                        naming.push(StrictName::from_name(long, &name)?);
231                    }
232                }
233                None => {
234                    return Err(Error::new(
235                        cons.span(),
236                        "This consumer needs a name, you can specify it with long(\"name\") or short('n')",
237                    ));
238                }
239            },
240
241            // doesn't need a name, got some, must complain
242            (false, true) => {
243                return Err(Error::new_spanned(
244                    ty,
245                    "field doesn't take a name annotation",
246                ));
247            }
248
249        };
250
251        let mut postpr = std::mem::take(&mut field_attrs.postpr);
252
253        let shape = split_type(&ty);
254
255        if let Consumer::Argument { ty, .. }
256        | Consumer::Positional { ty, .. }
257        | Consumer::Any { ty, .. } = &mut cons
258        {
259            if ty.is_none() {
260                match &shape {
261                    Shape::Optional(t) | Shape::Multiple(t) | Shape::Direct(t) => {
262                        *ty = Some(t.clone());
263                    }
264                    _ => {}
265                }
266            }
267        }
268
269        if derived_consumer {
270            for pp in &postpr {
271                if !pp.can_derive() {
272                    let err = Error::new(
273                        pp.span(),
274                        "Can't derive implicit consumer with this annotation present",
275                    );
276                    return Err(err);
277                }
278            }
279        }
280        let span = ty.span();
281
282        if !(postpr.iter().any(|p| matches!(p, Post::Parse(_)))
283            || matches!(cons, Consumer::External { .. } | Consumer::Pure { .. }))
284        {
285            match shape {
286                Shape::Optional(_) => postpr.insert(0, Post::Parse(PostParse::Optional { span })),
287                Shape::Multiple(_) => postpr.insert(0, Post::Parse(PostParse::Many { span })),
288                Shape::Bool => {
289                    if name.is_none()
290                        && naming.is_empty()
291                        && matches!(cons, Consumer::Switch { .. })
292                    {
293                        let msg = "Can't derive consumer for unnamed boolean field, try adding one of #[bpaf(positional)], #[bpaf(long(\"name\")] or #[bpaf(short('n'))] annotations to it";
294                        let err = Error::new_spanned(ty, msg);
295                        return Err(err);
296                    }
297                }
298                Shape::Unit | Shape::Direct(_) => {}
299            }
300        }
301
302        let help = match field_attrs.help.pop() {
303            Some(h) => Some(Help::Custom(h.doc)),
304            None => help,
305        };
306
307        Ok(StructField {
308            name,
309            env,
310            naming,
311            cons,
312            postpr,
313            help,
314        })
315    }
316}