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 | pub allow_unknown_fields: bool, |
30 | } |
31 | |
32 | impl<'a> Variant<'a> { |
33 | pub fn as_name(&'a self) -> &'a str { |
34 | &self.name_in_attr |
35 | } |
36 | |
37 | pub fn as_unit_match_arm(&'a self) -> UnitMatchArm<'a> { |
38 | UnitMatchArm(self) |
39 | } |
40 | |
41 | pub fn as_data_match_arm(&'a self) -> DataMatchArm<'a> { |
42 | DataMatchArm(self) |
43 | } |
44 | } |
45 | |
46 | impl<'a> UsesTypeParams for Variant<'a> { |
47 | fn uses_type_params<'b>( |
48 | &self, |
49 | options: &usage::Options, |
50 | type_set: &'b IdentSet, |
51 | ) -> IdentRefSet<'b> { |
52 | self.data.uses_type_params(options, type_set) |
53 | } |
54 | } |
55 | |
56 | /// Code generator for an enum variant in a unit match position. |
57 | /// This is placed in generated `from_string` calls for the parent enum. |
58 | /// Value-carrying variants wrapped in this type will emit code to produce an "unsupported format" error. |
59 | pub struct UnitMatchArm<'a>(&'a Variant<'a>); |
60 | |
61 | impl<'a> ToTokens for UnitMatchArm<'a> { |
62 | fn to_tokens(&self, tokens: &mut TokenStream) { |
63 | let val: &Variant<'a> = self.0; |
64 | |
65 | if val.skip { |
66 | return; |
67 | } |
68 | |
69 | let name_in_attr: &Cow<'_, String> = &val.name_in_attr; |
70 | |
71 | if val.data.is_unit() { |
72 | let variant_ident: &Ident = val.variant_ident; |
73 | let ty_ident: &Ident = val.ty_ident; |
74 | |
75 | tokens.append_all(iter:quote!( |
76 | #name_in_attr => ::darling::export::Ok(#ty_ident::#variant_ident), |
77 | )); |
78 | } else { |
79 | tokens.append_all(iter:quote!( |
80 | #name_in_attr => ::darling::export::Err(::darling::Error::unsupported_format("literal" )), |
81 | )); |
82 | } |
83 | } |
84 | } |
85 | |
86 | /// Code generator for an enum variant in a data-carrying match position. |
87 | /// This is placed in generated `from_list` calls for the parent enum. |
88 | /// Unit variants wrapped in this type will emit code to produce an "unsupported format" error. |
89 | pub struct DataMatchArm<'a>(&'a Variant<'a>); |
90 | |
91 | impl<'a> ToTokens for DataMatchArm<'a> { |
92 | fn to_tokens(&self, tokens: &mut TokenStream) { |
93 | let val: &Variant<'a> = self.0; |
94 | |
95 | if val.skip { |
96 | return; |
97 | } |
98 | |
99 | let name_in_attr = &val.name_in_attr; |
100 | let variant_ident = val.variant_ident; |
101 | let ty_ident = val.ty_ident; |
102 | |
103 | if val.data.is_unit() { |
104 | tokens.append_all(quote!( |
105 | #name_in_attr => ::darling::export::Err(::darling::Error::unsupported_format("list" )), |
106 | )); |
107 | |
108 | return; |
109 | } |
110 | |
111 | let vdg = FieldsGen::new(&val.data, val.allow_unknown_fields); |
112 | |
113 | if val.data.is_struct() { |
114 | let declare_errors = ErrorDeclaration::default(); |
115 | let check_errors = ErrorCheck::with_location(name_in_attr); |
116 | let require_fields = vdg.require_fields(); |
117 | let decls = vdg.declarations(); |
118 | let core_loop = vdg.core_loop(); |
119 | let inits = vdg.initializers(); |
120 | |
121 | tokens.append_all(quote!( |
122 | #name_in_attr => { |
123 | if let ::darling::export::syn::Meta::List(ref __data) = *__nested { |
124 | let __items = ::darling::export::NestedMeta::parse_meta_list(__data.tokens.clone())?; |
125 | let __items = &__items; |
126 | |
127 | #declare_errors |
128 | |
129 | #decls |
130 | |
131 | #core_loop |
132 | |
133 | #require_fields |
134 | |
135 | #check_errors |
136 | |
137 | ::darling::export::Ok(#ty_ident::#variant_ident { |
138 | #inits |
139 | }) |
140 | } else { |
141 | ::darling::export::Err(::darling::Error::unsupported_format("non-list" )) |
142 | } |
143 | } |
144 | )); |
145 | } else if val.data.is_newtype() { |
146 | tokens.append_all(quote!( |
147 | #name_in_attr => { |
148 | ::darling::export::Ok( |
149 | #ty_ident::#variant_ident( |
150 | ::darling::FromMeta::from_meta(__nested) |
151 | .map_err(|e| e.at(#name_in_attr))?) |
152 | ) |
153 | } |
154 | )); |
155 | } else { |
156 | panic!("Match arms aren't supported for tuple variants yet" ); |
157 | } |
158 | } |
159 | } |
160 | |