1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
use std::borrow::Cow;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::Ident;

use crate::ast::Fields;
use crate::codegen::error::{ErrorCheck, ErrorDeclaration};
use crate::codegen::{Field, FieldsGen};
use crate::usage::{self, IdentRefSet, IdentSet, UsesTypeParams};

/// A variant of the enum which is deriving `FromMeta`.
#[derive(Debug, Clone)]
pub struct Variant<'a> {
    /// The name which will appear in code passed to the `FromMeta` input.
    pub name_in_attr: Cow<'a, String>,

    /// The name of the variant which will be returned for a given `name_in_attr`.
    pub variant_ident: &'a Ident,

    /// The name of the parent enum type.
    pub ty_ident: &'a Ident,

    pub data: Fields<Field<'a>>,

    /// Whether or not the variant should be skipped in the generated code.
    pub skip: bool,

    /// Whether or not the variant should be used to create an instance for
    /// `FromMeta::from_word`.
    pub word: bool,

    pub allow_unknown_fields: bool,
}

impl<'a> Variant<'a> {
    pub fn as_name(&'a self) -> &'a str {
        &self.name_in_attr
    }

    pub fn as_unit_match_arm(&'a self) -> UnitMatchArm<'a> {
        UnitMatchArm(self)
    }

    pub fn as_data_match_arm(&'a self) -> DataMatchArm<'a> {
        DataMatchArm(self)
    }
}

impl<'a> UsesTypeParams for Variant<'a> {
    fn uses_type_params<'b>(
        &self,
        options: &usage::Options,
        type_set: &'b IdentSet,
    ) -> IdentRefSet<'b> {
        self.data.uses_type_params(options, type_set)
    }
}

impl<'a> ToTokens for Variant<'a> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        if self.data.is_unit() {
            self.as_unit_match_arm().to_tokens(tokens);
        } else {
            self.as_data_match_arm().to_tokens(tokens)
        }
    }
}

/// Code generator for an enum variant in a unit match position.
/// This is placed in generated `from_string` calls for the parent enum.
/// Value-carrying variants wrapped in this type will emit code to produce an "unsupported format" error.
pub struct UnitMatchArm<'a>(&'a Variant<'a>);

impl<'a> ToTokens for UnitMatchArm<'a> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let val: &Variant<'a> = self.0;

        if val.skip {
            return;
        }

        let name_in_attr = &val.name_in_attr;

        let unsupported_format_error = || {
            quote!(::darling::export::Err(
                ::darling::Error::unsupported_format("literal")
            ))
        };

        if val.data.is_unit() {
            let variant_ident = val.variant_ident;
            let ty_ident = val.ty_ident;

            tokens.append_all(quote!(
                #name_in_attr => ::darling::export::Ok(#ty_ident::#variant_ident),
            ));
        } else if val.data.is_newtype() {
            let field = val
                .data
                .fields
                .first()
                .expect("Newtype should have exactly one field");
            let field_ty = field.ty;
            let ty_ident = val.ty_ident;
            let variant_ident = val.variant_ident;
            let unsupported_format = unsupported_format_error();

            tokens.append_all(quote!{
                #name_in_attr => {
                    match <#field_ty as ::darling::FromMeta>::from_none() {
                        ::darling::export::Some(__value) => ::darling::export::Ok(#ty_ident::#variant_ident(__value)),
                        ::darling::export::None => #unsupported_format,
                    }
                }
            })
        } else {
            let unsupported_format = unsupported_format_error();
            tokens.append_all(quote!(
                #name_in_attr => #unsupported_format,
            ));
        }
    }
}

/// Code generator for an enum variant in a data-carrying match position.
/// This is placed in generated `from_list` calls for the parent enum.
/// Unit variants wrapped in this type will emit code to produce an "unsupported format" error.
pub struct DataMatchArm<'a>(&'a Variant<'a>);

impl<'a> ToTokens for DataMatchArm<'a> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let val: &Variant<'a> = self.0;

        if val.skip {
            return;
        }

        let name_in_attr = &val.name_in_attr;
        let variant_ident = val.variant_ident;
        let ty_ident = val.ty_ident;

        if val.data.is_unit() {
            tokens.append_all(quote!(
                #name_in_attr => ::darling::export::Err(::darling::Error::unsupported_format("list")),
            ));

            return;
        }

        let vdg = FieldsGen::new(&val.data, val.allow_unknown_fields);

        if val.data.is_struct() {
            let declare_errors = ErrorDeclaration::default();
            let check_errors = ErrorCheck::with_location(name_in_attr);
            let require_fields = vdg.require_fields();
            let decls = vdg.declarations();
            let core_loop = vdg.core_loop();
            let inits = vdg.initializers();

            tokens.append_all(quote!(
                #name_in_attr => {
                    if let ::darling::export::syn::Meta::List(ref __data) = *__nested {
                        let __items = ::darling::export::NestedMeta::parse_meta_list(__data.tokens.clone())?;
                        let __items = &__items;

                        #declare_errors

                        #decls

                        #core_loop

                        #require_fields

                        #check_errors

                        ::darling::export::Ok(#ty_ident::#variant_ident {
                            #inits
                        })
                    } else {
                        ::darling::export::Err(::darling::Error::unsupported_format("non-list"))
                    }
                }
            ));
        } else if val.data.is_newtype() {
            tokens.append_all(quote!(
                #name_in_attr => {
                    ::darling::export::Ok(
                        #ty_ident::#variant_ident(
                            ::darling::FromMeta::from_meta(__nested)
                                .map_err(|e| e.at(#name_in_attr))?)
                    )
                }
            ));
        } else {
            panic!("Match arms aren't supported for tuple variants yet");
        }
    }
}