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 = &__data.nested;
125
126 #declare_errors
127
128 #decls
129
130 #core_loop
131
132 #require_fields
133
134 #check_errors
135
136 ::darling::export::Ok(#ty_ident::#variant_ident {
137 #inits
138 })
139 } else {
140 ::darling::export::Err(::darling::Error::unsupported_format("non-list"))
141 }
142 }
143 ));
144 } else if val.data.is_newtype() {
145 tokens.append_all(quote!(
146 #name_in_attr => {
147 ::darling::export::Ok(
148 #ty_ident::#variant_ident(
149 ::darling::FromMeta::from_meta(__nested)
150 .map_err(|e| e.at(#name_in_attr))?)
151 )
152 }
153 ));
154 } else {
155 panic!("Match arms aren't supported for tuple variants yet");
156 }
157 }
158}
159