• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::borrow::Cow;
2 
3 use proc_macro2::TokenStream;
4 use quote::{quote, ToTokens, TokenStreamExt};
5 use syn::Ident;
6 
7 use crate::ast::Fields;
8 use crate::codegen::error::{ErrorCheck, ErrorDeclaration};
9 use crate::codegen::{Field, FieldsGen};
10 use crate::usage::{self, IdentRefSet, IdentSet, UsesTypeParams};
11 
12 /// A variant of the enum which is deriving `FromMeta`.
13 #[derive(Debug, Clone)]
14 pub struct Variant<'a> {
15     /// The name which will appear in code passed to the `FromMeta` input.
16     pub name_in_attr: Cow<'a, String>,
17 
18     /// The name of the variant which will be returned for a given `name_in_attr`.
19     pub variant_ident: &'a Ident,
20 
21     /// The name of the parent enum type.
22     pub ty_ident: &'a Ident,
23 
24     pub data: Fields<Field<'a>>,
25 
26     /// Whether or not the variant should be skipped in the generated code.
27     pub skip: bool,
28 
29     /// Whether or not the variant should be used to create an instance for
30     /// `FromMeta::from_word`.
31     pub word: bool,
32 
33     pub allow_unknown_fields: bool,
34 }
35 
36 impl<'a> Variant<'a> {
as_name(&'a self) -> &'a str37     pub fn as_name(&'a self) -> &'a str {
38         &self.name_in_attr
39     }
40 
as_unit_match_arm(&'a self) -> UnitMatchArm<'a>41     pub fn as_unit_match_arm(&'a self) -> UnitMatchArm<'a> {
42         UnitMatchArm(self)
43     }
44 
as_data_match_arm(&'a self) -> DataMatchArm<'a>45     pub fn as_data_match_arm(&'a self) -> DataMatchArm<'a> {
46         DataMatchArm(self)
47     }
48 }
49 
50 impl<'a> UsesTypeParams for Variant<'a> {
uses_type_params<'b>( &self, options: &usage::Options, type_set: &'b IdentSet, ) -> IdentRefSet<'b>51     fn uses_type_params<'b>(
52         &self,
53         options: &usage::Options,
54         type_set: &'b IdentSet,
55     ) -> IdentRefSet<'b> {
56         self.data.uses_type_params(options, type_set)
57     }
58 }
59 
60 impl<'a> ToTokens for Variant<'a> {
to_tokens(&self, tokens: &mut TokenStream)61     fn to_tokens(&self, tokens: &mut TokenStream) {
62         if self.data.is_unit() {
63             self.as_unit_match_arm().to_tokens(tokens);
64         } else {
65             self.as_data_match_arm().to_tokens(tokens)
66         }
67     }
68 }
69 
70 /// Code generator for an enum variant in a unit match position.
71 /// This is placed in generated `from_string` calls for the parent enum.
72 /// Value-carrying variants wrapped in this type will emit code to produce an "unsupported format" error.
73 pub struct UnitMatchArm<'a>(&'a Variant<'a>);
74 
75 impl<'a> ToTokens for UnitMatchArm<'a> {
to_tokens(&self, tokens: &mut TokenStream)76     fn to_tokens(&self, tokens: &mut TokenStream) {
77         let val: &Variant<'a> = self.0;
78 
79         if val.skip {
80             return;
81         }
82 
83         let name_in_attr = &val.name_in_attr;
84 
85         let unsupported_format_error = || {
86             quote!(::darling::export::Err(
87                 ::darling::Error::unsupported_format("literal")
88             ))
89         };
90 
91         if val.data.is_unit() {
92             let variant_ident = val.variant_ident;
93             let ty_ident = val.ty_ident;
94 
95             tokens.append_all(quote!(
96                 #name_in_attr => ::darling::export::Ok(#ty_ident::#variant_ident),
97             ));
98         } else if val.data.is_newtype() {
99             let field = val
100                 .data
101                 .fields
102                 .first()
103                 .expect("Newtype should have exactly one field");
104             let field_ty = field.ty;
105             let ty_ident = val.ty_ident;
106             let variant_ident = val.variant_ident;
107             let unsupported_format = unsupported_format_error();
108 
109             tokens.append_all(quote!{
110                 #name_in_attr => {
111                     match <#field_ty as ::darling::FromMeta>::from_none() {
112                         ::darling::export::Some(__value) => ::darling::export::Ok(#ty_ident::#variant_ident(__value)),
113                         ::darling::export::None => #unsupported_format,
114                     }
115                 }
116             })
117         } else {
118             let unsupported_format = unsupported_format_error();
119             tokens.append_all(quote!(
120                 #name_in_attr => #unsupported_format,
121             ));
122         }
123     }
124 }
125 
126 /// Code generator for an enum variant in a data-carrying match position.
127 /// This is placed in generated `from_list` calls for the parent enum.
128 /// Unit variants wrapped in this type will emit code to produce an "unsupported format" error.
129 pub struct DataMatchArm<'a>(&'a Variant<'a>);
130 
131 impl<'a> ToTokens for DataMatchArm<'a> {
to_tokens(&self, tokens: &mut TokenStream)132     fn to_tokens(&self, tokens: &mut TokenStream) {
133         let val: &Variant<'a> = self.0;
134 
135         if val.skip {
136             return;
137         }
138 
139         let name_in_attr = &val.name_in_attr;
140         let variant_ident = val.variant_ident;
141         let ty_ident = val.ty_ident;
142 
143         if val.data.is_unit() {
144             tokens.append_all(quote!(
145                 #name_in_attr => ::darling::export::Err(::darling::Error::unsupported_format("list")),
146             ));
147 
148             return;
149         }
150 
151         let vdg = FieldsGen::new(&val.data, val.allow_unknown_fields);
152 
153         if val.data.is_struct() {
154             let declare_errors = ErrorDeclaration::default();
155             let check_errors = ErrorCheck::with_location(name_in_attr);
156             let require_fields = vdg.require_fields();
157             let decls = vdg.declarations();
158             let core_loop = vdg.core_loop();
159             let inits = vdg.initializers();
160 
161             tokens.append_all(quote!(
162                 #name_in_attr => {
163                     if let ::darling::export::syn::Meta::List(ref __data) = *__nested {
164                         let __items = ::darling::export::NestedMeta::parse_meta_list(__data.tokens.clone())?;
165                         let __items = &__items;
166 
167                         #declare_errors
168 
169                         #decls
170 
171                         #core_loop
172 
173                         #require_fields
174 
175                         #check_errors
176 
177                         ::darling::export::Ok(#ty_ident::#variant_ident {
178                             #inits
179                         })
180                     } else {
181                         ::darling::export::Err(::darling::Error::unsupported_format("non-list"))
182                     }
183                 }
184             ));
185         } else if val.data.is_newtype() {
186             tokens.append_all(quote!(
187                 #name_in_attr => {
188                     ::darling::export::Ok(
189                         #ty_ident::#variant_ident(
190                             ::darling::FromMeta::from_meta(__nested)
191                                 .map_err(|e| e.at(#name_in_attr))?)
192                     )
193                 }
194             ));
195         } else {
196             panic!("Match arms aren't supported for tuple variants yet");
197         }
198     }
199 }
200