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>, /// 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; 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 { tokens.append_all(quote!( #name_in_attr => ::darling::export::Err(::darling::Error::unsupported_format("literal")), )); } } } /// 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"); } } }