1 use std::borrow::Cow;
2 
3 use proc_macro2::TokenStream;
4 use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
5 use syn::{spanned::Spanned, Ident, Path, Type};
6 
7 use crate::codegen::{DefaultExpression, PostfixTransform};
8 use crate::usage::{self, IdentRefSet, IdentSet, UsesTypeParams};
9 
10 /// Properties needed to generate code for a field in all the contexts
11 /// where one may appear.
12 #[derive(Debug, Clone)]
13 pub struct Field<'a> {
14     /// The name presented to the user of the library. This will appear
15     /// in error messages and will be looked when parsing names.
16     pub name_in_attr: Cow<'a, String>,
17 
18     /// The name presented to the author of the library. This will appear
19     /// in the setters or temporary variables which contain the values.
20     pub ident: &'a Ident,
21 
22     /// The type of the field in the input.
23     pub ty: &'a Type,
24     pub default_expression: Option<DefaultExpression<'a>>,
25     pub with_path: Cow<'a, Path>,
26     pub post_transform: Option<&'a PostfixTransform>,
27     pub skip: bool,
28     pub multiple: bool,
29     /// If set, this field will be given all unclaimed meta items and will
30     /// not be exposed as a standard named field.
31     pub flatten: bool,
32 }
33 
34 impl<'a> Field<'a> {
35     /// Get the name of the meta item that should be matched against input and should be used in diagnostics.
36     ///
37     /// This will be `None` if the field is `skip` or `flatten`, as neither kind of field is addressable
38     /// by name from the input meta.
as_name(&'a self) -> Option<&'a str>39     pub fn as_name(&'a self) -> Option<&'a str> {
40         if self.skip || self.flatten {
41             None
42         } else {
43             Some(&self.name_in_attr)
44         }
45     }
46 
as_declaration(&'a self) -> Declaration<'a>47     pub fn as_declaration(&'a self) -> Declaration<'a> {
48         Declaration(self)
49     }
50 
as_flatten_initializer( &'a self, parent_field_names: Vec<&'a str>, ) -> FlattenInitializer<'a>51     pub fn as_flatten_initializer(
52         &'a self,
53         parent_field_names: Vec<&'a str>,
54     ) -> FlattenInitializer<'a> {
55         FlattenInitializer {
56             field: self,
57             parent_field_names,
58         }
59     }
60 
as_match(&'a self) -> MatchArm<'a>61     pub fn as_match(&'a self) -> MatchArm<'a> {
62         MatchArm(self)
63     }
64 
as_initializer(&'a self) -> Initializer<'a>65     pub fn as_initializer(&'a self) -> Initializer<'a> {
66         Initializer(self)
67     }
68 
as_presence_check(&'a self) -> CheckMissing<'a>69     pub fn as_presence_check(&'a self) -> CheckMissing<'a> {
70         CheckMissing(self)
71     }
72 }
73 
74 impl<'a> UsesTypeParams for Field<'a> {
uses_type_params<'b>( &self, options: &usage::Options, type_set: &'b IdentSet, ) -> IdentRefSet<'b>75     fn uses_type_params<'b>(
76         &self,
77         options: &usage::Options,
78         type_set: &'b IdentSet,
79     ) -> IdentRefSet<'b> {
80         self.ty.uses_type_params(options, type_set)
81     }
82 }
83 
84 /// An individual field during variable declaration in the generated parsing method.
85 pub struct Declaration<'a>(&'a Field<'a>);
86 
87 impl<'a> ToTokens for Declaration<'a> {
to_tokens(&self, tokens: &mut TokenStream)88     fn to_tokens(&self, tokens: &mut TokenStream) {
89         let field = self.0;
90         let ident = field.ident;
91         let ty = field.ty;
92 
93         tokens.append_all(if field.multiple {
94             // This is NOT mutable, as it will be declared mutable only temporarily.
95             quote!(let mut #ident: #ty = ::darling::export::Default::default();)
96         } else {
97             quote!(let mut #ident: (bool, ::darling::export::Option<#ty>) = (false, None);)
98         });
99 
100         // The flatten field additionally needs a place to buffer meta items
101         // until attribute walking is done, so declare that now.
102         //
103         // We expect there can only be one field marked `flatten`, so it shouldn't
104         // be possible for this to shadow another declaration.
105         if field.flatten {
106             tokens.append_all(quote! {
107                 let mut __flatten: Vec<::darling::ast::NestedMeta> = vec![];
108             });
109         }
110     }
111 }
112 
113 pub struct FlattenInitializer<'a> {
114     field: &'a Field<'a>,
115     parent_field_names: Vec<&'a str>,
116 }
117 
118 impl<'a> ToTokens for FlattenInitializer<'a> {
to_tokens(&self, tokens: &mut TokenStream)119     fn to_tokens(&self, tokens: &mut TokenStream) {
120         let Self {
121             field,
122             parent_field_names,
123         } = self;
124         let ident = field.ident;
125 
126         let add_parent_fields = if parent_field_names.is_empty() {
127             None
128         } else {
129             Some(quote! {
130                 .map_err(|e| e.add_sibling_alts_for_unknown_field(&[#(#parent_field_names),*]))
131             })
132         };
133 
134         tokens.append_all(quote! {
135             #ident = (true,
136                 __errors.handle(
137                     ::darling::FromMeta::from_list(&__flatten) #add_parent_fields
138                     )
139                 );
140         });
141     }
142 }
143 
144 /// Represents an individual field in the match.
145 pub struct MatchArm<'a>(&'a Field<'a>);
146 
147 impl<'a> ToTokens for MatchArm<'a> {
to_tokens(&self, tokens: &mut TokenStream)148     fn to_tokens(&self, tokens: &mut TokenStream) {
149         let field = self.0;
150 
151         // Skipped and flattened fields cannot be populated by a meta
152         // with their name, so they do not have a match arm.
153         if field.skip || field.flatten {
154             return;
155         }
156 
157         let name_str = &field.name_in_attr;
158         let ident = field.ident;
159         let with_path = &field.with_path;
160         let post_transform = field.post_transform.as_ref();
161 
162         // Errors include the location of the bad input, so we compute that here.
163         // Fields that take multiple values add the index of the error for convenience,
164         // while single-value fields only expose the name in the input attribute.
165         let location = if field.multiple {
166             // we use the local variable `len` here because location is accessed via
167             // a closure, and the borrow checker gets very unhappy if we try to immutably
168             // borrow `#ident` in that closure when it was declared `mut` outside.
169             quote!(&format!("{}[{}]", #name_str, __len))
170         } else {
171             quote!(#name_str)
172         };
173 
174         // Give darling's generated code the span of the `with_path` so that if the target
175         // type doesn't impl FromMeta, darling's immediate user gets a properly-spanned error.
176         //
177         // Within the generated code, add the span immediately on extraction failure, so that it's
178         // as specific as possible.
179         // The behavior of `with_span` makes this safe to do; if the child applied an
180         // even-more-specific span, our attempt here will not overwrite that and will only cost
181         // us one `if` check.
182         let extractor = quote_spanned!(with_path.span()=>#with_path(__inner)#post_transform.map_err(|e| e.with_span(&__inner).at(#location)));
183 
184         tokens.append_all(if field.multiple {
185                 quote!(
186                     #name_str => {
187                         // Store the index of the name we're assessing in case we need
188                         // it for error reporting.
189                         let __len = #ident.len();
190                         if let ::darling::export::Some(__val) = __errors.handle(#extractor) {
191                             #ident.push(__val)
192                         }
193                     }
194                 )
195             } else {
196                 quote!(
197                     #name_str => {
198                         if !#ident.0 {
199                             #ident = (true, __errors.handle(#extractor));
200                         } else {
201                             __errors.push(::darling::Error::duplicate_field(#name_str).with_span(&__inner));
202                         }
203                     }
204                 )
205             });
206     }
207 }
208 
209 /// Wrapper to generate initialization code for a field.
210 pub struct Initializer<'a>(&'a Field<'a>);
211 
212 impl<'a> ToTokens for Initializer<'a> {
to_tokens(&self, tokens: &mut TokenStream)213     fn to_tokens(&self, tokens: &mut TokenStream) {
214         let field = self.0;
215         let ident = field.ident;
216         tokens.append_all(if field.multiple {
217             if let Some(ref expr) = field.default_expression {
218                 quote_spanned!(expr.span()=> #ident: if !#ident.is_empty() {
219                     #ident
220                 } else {
221                     #expr
222                 })
223             } else {
224                 quote!(#ident: #ident)
225             }
226         } else if let Some(ref expr) = field.default_expression {
227             quote_spanned!(expr.span()=> #ident: if let Some(__val) = #ident.1 {
228                 __val
229             } else {
230                 #expr
231             })
232         } else {
233             quote!(#ident: #ident.1.expect("Uninitialized fields without defaults were already checked"))
234         });
235     }
236 }
237 
238 /// Creates an error if a field has no value and no default.
239 pub struct CheckMissing<'a>(&'a Field<'a>);
240 
241 impl<'a> ToTokens for CheckMissing<'a> {
to_tokens(&self, tokens: &mut TokenStream)242     fn to_tokens(&self, tokens: &mut TokenStream) {
243         if !self.0.multiple && self.0.default_expression.is_none() {
244             let ident = self.0.ident;
245             let ty = self.0.ty;
246             let name_in_attr = &self.0.name_in_attr;
247 
248             // If `ty` does not impl FromMeta, the compiler error should point
249             // at the offending type rather than at the derive-macro call site.
250             let from_none_call =
251                 quote_spanned!(ty.span()=> <#ty as ::darling::FromMeta>::from_none());
252 
253             tokens.append_all(quote! {
254                 if !#ident.0 {
255                     match #from_none_call {
256                         ::darling::export::Some(__type_fallback) => {
257                             #ident.1 = ::darling::export::Some(__type_fallback);
258                         }
259                         ::darling::export::None => {
260                             __errors.push(::darling::Error::missing_field(#name_in_attr))
261                         }
262                     }
263                 }
264             })
265         }
266     }
267 }
268