1use std::borrow::Cow;
2
3use proc_macro2::TokenStream;
4use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
5use syn::{spanned::Spanned, Ident, Path, Type};
6
7use crate::codegen::{DefaultExpression, PostfixTransform};
8use 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)]
13pub 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}
30
31impl<'a> Field<'a> {
32 pub fn as_name(&'a self) -> &'a str {
33 &self.name_in_attr
34 }
35
36 pub fn as_declaration(&'a self) -> Declaration<'a> {
37 Declaration(self)
38 }
39
40 pub fn as_match(&'a self) -> MatchArm<'a> {
41 MatchArm(self)
42 }
43
44 pub fn as_initializer(&'a self) -> Initializer<'a> {
45 Initializer(self)
46 }
47
48 pub fn as_presence_check(&'a self) -> CheckMissing<'a> {
49 CheckMissing(self)
50 }
51}
52
53impl<'a> UsesTypeParams for Field<'a> {
54 fn uses_type_params<'b>(
55 &self,
56 options: &usage::Options,
57 type_set: &'b IdentSet,
58 ) -> IdentRefSet<'b> {
59 self.ty.uses_type_params(options, type_set)
60 }
61}
62
63/// An individual field during variable declaration in the generated parsing method.
64pub struct Declaration<'a>(&'a Field<'a>);
65
66impl<'a> ToTokens for Declaration<'a> {
67 fn to_tokens(&self, tokens: &mut TokenStream) {
68 let field: &Field = self.0;
69 let ident: &Ident = field.ident;
70 let ty: &Type = field.ty;
71
72 tokens.append_all(iter:if field.multiple {
73 // This is NOT mutable, as it will be declared mutable only temporarily.
74 quote!(let mut #ident: #ty = ::darling::export::Default::default();)
75 } else {
76 quote!(let mut #ident: (bool, ::darling::export::Option<#ty>) = (false, None);)
77 });
78 }
79}
80
81/// Represents an individual field in the match.
82pub struct MatchArm<'a>(&'a Field<'a>);
83
84impl<'a> ToTokens for MatchArm<'a> {
85 fn to_tokens(&self, tokens: &mut TokenStream) {
86 let field: &Field = self.0;
87 if !field.skip {
88 let name_str = &field.name_in_attr;
89 let ident = field.ident;
90 let with_path = &field.with_path;
91 let post_transform = field.post_transform.as_ref();
92
93 // Errors include the location of the bad input, so we compute that here.
94 // Fields that take multiple values add the index of the error for convenience,
95 // while single-value fields only expose the name in the input attribute.
96 let location = if field.multiple {
97 // we use the local variable `len` here because location is accessed via
98 // a closure, and the borrow checker gets very unhappy if we try to immutably
99 // borrow `#ident` in that closure when it was declared `mut` outside.
100 quote!(&format!("{}[{}]", #name_str, __len))
101 } else {
102 quote!(#name_str)
103 };
104
105 // Give darling's generated code the span of the `with_path` so that if the target
106 // type doesn't impl FromMeta, darling's immediate user gets a properly-spanned error.
107 //
108 // Within the generated code, add the span immediately on extraction failure, so that it's
109 // as specific as possible.
110 // The behavior of `with_span` makes this safe to do; if the child applied an
111 // even-more-specific span, our attempt here will not overwrite that and will only cost
112 // us one `if` check.
113 let extractor = quote_spanned!(with_path.span()=>#with_path(__inner)#post_transform.map_err(|e| e.with_span(&__inner).at(#location)));
114
115 tokens.append_all(if field.multiple {
116 quote!(
117 #name_str => {
118 // Store the index of the name we're assessing in case we need
119 // it for error reporting.
120 let __len = #ident.len();
121 if let ::darling::export::Some(__val) = __errors.handle(#extractor) {
122 #ident.push(__val)
123 }
124 }
125 )
126 } else {
127 quote!(
128 #name_str => {
129 if !#ident.0 {
130 #ident = (true, __errors.handle(#extractor));
131 } else {
132 __errors.push(::darling::Error::duplicate_field(#name_str).with_span(&__inner));
133 }
134 }
135 )
136 });
137 }
138 }
139}
140
141/// Wrapper to generate initialization code for a field.
142pub struct Initializer<'a>(&'a Field<'a>);
143
144impl<'a> ToTokens for Initializer<'a> {
145 fn to_tokens(&self, tokens: &mut TokenStream) {
146 let field: &Field = self.0;
147 let ident = field.ident;
148 tokens.append_all(if field.multiple {
149 if let Some(ref expr) = field.default_expression {
150 quote_spanned!(expr.span()=> #ident: if !#ident.is_empty() {
151 #ident
152 } else {
153 #expr
154 })
155 } else {
156 quote!(#ident: #ident)
157 }
158 } else if let Some(ref expr) = field.default_expression {
159 quote_spanned!(expr.span()=> #ident: if let Some(__val) = #ident.1 {
160 __val
161 } else {
162 #expr
163 })
164 } else {
165 quote!(#ident: #ident.1.expect("Uninitialized fields without defaults were already checked"))
166 });
167 }
168}
169
170/// Creates an error if a field has no value and no default.
171pub struct CheckMissing<'a>(&'a Field<'a>);
172
173impl<'a> ToTokens for CheckMissing<'a> {
174 fn to_tokens(&self, tokens: &mut TokenStream) {
175 if !self.0.multiple && self.0.default_expression.is_none() {
176 let ident = self.0.ident;
177 let ty = self.0.ty;
178 let name_in_attr = &self.0.name_in_attr;
179
180 // If `ty` does not impl FromMeta, the compiler error should point
181 // at the offending type rather than at the derive-macro call site.
182 let from_none_call =
183 quote_spanned!(ty.span()=> <#ty as ::darling::FromMeta>::from_none());
184
185 tokens.append_all(quote! {
186 if !#ident.0 {
187 match #from_none_call {
188 ::darling::export::Some(__type_fallback) => {
189 #ident.1 = ::darling::export::Some(__type_fallback);
190 }
191 ::darling::export::None => {
192 __errors.push(::darling::Error::missing_field(#name_in_attr))
193 }
194 }
195 }
196 })
197 }
198 }
199}
200