darling_core/options/
from_meta.rs

1use std::borrow::Cow;
2
3use proc_macro2::TokenStream;
4use quote::ToTokens;
5use syn::parse_quote;
6
7use crate::ast::Data;
8use crate::codegen::FromMetaImpl;
9use crate::error::Accumulator;
10use crate::options::{Core, ParseAttribute, ParseData};
11use crate::util::Callable;
12use crate::{Error, FromMeta, Result};
13
14pub struct FromMetaOptions {
15    base: Core,
16    /// Override for the default [`FromMeta::from_word`] method.
17    from_word: Option<Callable>,
18    /// Override for the default [`FromMeta::from_none`] method.
19    from_none: Option<Callable>,
20}
21
22impl FromMetaOptions {
23    pub fn new(di: &syn::DeriveInput) -> Result<Self> {
24        (FromMetaOptions {
25            base: Core::start(di)?,
26            from_word: None,
27            from_none: None,
28        })
29        .parse_attributes(&di.attrs)?
30        .parse_body(&di.data)
31    }
32
33    /// Get the `from_word` method body, if one exists. This can come from direct use of
34    /// `#[darling(from_word = ...)]` on the container or from use of `#[darling(word)]` on
35    /// a unit variant.
36    #[allow(
37        clippy::wrong_self_convention,
38        // The reason is commented out due to MSRV issues.
39        // reason = "This matches the name of the input option and output method"
40    )]
41    fn from_word(&self) -> Option<Cow<'_, Callable>> {
42        self.from_word.as_ref().map(Cow::Borrowed).or_else(|| {
43            if let Data::Enum(ref variants) = self.base.data {
44                // The first variant which has `word` set to `true`.
45                // This assumes that validation has prevented multiple variants
46                // from claiming `word`.
47                let variant = variants
48                    .iter()
49                    .find(|v| v.word.map(|x| *x).unwrap_or_default())?;
50                let variant_ident = &variant.ident;
51                let closure: syn::ExprClosure = parse_quote! {
52                    || ::darling::export::Ok(Self::#variant_ident)
53                };
54                Some(Cow::Owned(Callable::from(closure)))
55            } else {
56                None
57            }
58        })
59    }
60}
61
62impl ParseAttribute for FromMetaOptions {
63    fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
64        let path = mi.path();
65
66        if path.is_ident("from_word") {
67            if self.from_word.is_some() {
68                return Err(Error::duplicate_field_path(path).with_span(path));
69            }
70
71            self.from_word = FromMeta::from_meta(mi).map(Some)?;
72        } else if path.is_ident("from_none") {
73            if self.from_none.is_some() {
74                return Err(Error::duplicate_field_path(path).with_span(path));
75            }
76
77            self.from_none = FromMeta::from_meta(mi).map(Some)?;
78        } else {
79            self.base.parse_nested(mi)?;
80        }
81
82        Ok(())
83    }
84}
85
86impl ParseData for FromMetaOptions {
87    fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
88        self.base.parse_variant(variant)
89    }
90
91    fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
92        self.base.parse_field(field)
93    }
94
95    fn validate_body(&self, errors: &mut Accumulator) {
96        self.base.validate_body(errors);
97
98        match self.base.data {
99            Data::Struct(ref data) => {
100                if let Some(from_word) = &self.from_word {
101                    if data.is_unit() {
102                        errors.push(Error::custom("`from_word` cannot be used on unit structs because it conflicts with the generated impl").with_span(from_word));
103                    } else if data.is_newtype() {
104                        errors.push(Error::custom("`from_word` cannot be used on newtype structs because the implementation is entirely delegated to the inner type").with_span(from_word));
105                    }
106                }
107            }
108            Data::Enum(ref data) => {
109                let word_variants: Vec<_> = data
110                    .iter()
111                    .filter_map(|variant| variant.word.as_ref())
112                    .collect();
113
114                if !word_variants.is_empty() {
115                    if let Some(from_word) = &self.from_word {
116                        errors.push(
117                            Error::custom(
118                                "`from_word` cannot be used with an enum that also uses `word`",
119                            )
120                            .with_span(from_word),
121                        )
122                    }
123                }
124
125                // Adds errors for duplicate `#[darling(word)]` annotations across all variants.
126                if word_variants.len() > 1 {
127                    for word in word_variants {
128                        errors.push(
129                            Error::custom("`#[darling(word)]` can only be applied to one variant")
130                                .with_span(&word.span()),
131                        );
132                    }
133                }
134            }
135        }
136    }
137}
138
139impl<'a> From<&'a FromMetaOptions> for FromMetaImpl<'a> {
140    fn from(v: &'a FromMetaOptions) -> Self {
141        FromMetaImpl {
142            base: (&v.base).into(),
143            from_word: v.from_word(),
144            from_none: v.from_none.as_ref(),
145        }
146    }
147}
148
149impl ToTokens for FromMetaOptions {
150    fn to_tokens(&self, tokens: &mut TokenStream) {
151        FromMetaImpl::from(self).to_tokens(tokens)
152    }
153}