summaryrefslogtreecommitdiff
path: root/src/codegen/variant.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/codegen/variant.rs')
-rw-r--r--src/codegen/variant.rs173
1 files changed, 173 insertions, 0 deletions
diff --git a/src/codegen/variant.rs b/src/codegen/variant.rs
new file mode 100644
index 0000000..2a55780
--- /dev/null
+++ b/src/codegen/variant.rs
@@ -0,0 +1,173 @@
+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;
+
+ 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");
+ }
+ }
+}