1use std::borrow::Cow;
2
3use proc_macro2::TokenStream;
4use quote::{quote, ToTokens, TokenStreamExt};
5use syn::Ident;
6
7use crate::ast::Fields;
8use crate::codegen::error::{ErrorCheck, ErrorDeclaration};
9use crate::codegen::{Field, FieldsGen};
10use crate::usage::{self, IdentRefSet, IdentSet, UsesTypeParams};
11
12/// A variant of the enum which is deriving `FromMeta`.
13#[derive(Debug, Clone)]
14pub 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
32impl<'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
46impl<'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.
59pub struct UnitMatchArm<'a>(&'a Variant<'a>);
60
61impl<'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.
89pub struct DataMatchArm<'a>(&'a Variant<'a>);
90
91impl<'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