diff --git a/codegen/src/clone.rs b/codegen/src/clone.rs index 0dbb67b34..92dbc9669 100644 --- a/codegen/src/clone.rs +++ b/codegen/src/clone.rs @@ -1,4 +1,4 @@ -use crate::{cfg, file, lookup}; +use crate::{cfg, file, full, lookup}; use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; @@ -13,6 +13,7 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { match &node.data { Data::Enum(variants) if variants.is_empty() => quote!(match *self {}), Data::Enum(variants) => { + let mixed_derive_full = full::is_mixed_derive_full_enum(defs, node); let arms = variants.iter().map(|(variant_name, fields)| { let variant = Ident::new(variant_name, Span::call_site()); if fields.is_empty() { @@ -28,9 +29,10 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { pats.push(pat); } let mut cfg = None; - if node.ident == "Expr" { + if mixed_derive_full { if let Type::Syn(ty) = &fields[0] { - if !lookup::node(defs, ty).features.any.contains("derive") { + let features = &lookup::node(defs, ty).features; + if features.any.contains("full") && !features.any.contains("derive") { cfg = Some(quote!(#[cfg(feature = "full")])); } } @@ -41,7 +43,7 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { } } }); - let nonexhaustive = if node.ident == "Expr" { + let nonexhaustive = if mixed_derive_full { Some(quote! { #[cfg(not(feature = "full"))] _ => unreachable!(), diff --git a/codegen/src/debug.rs b/codegen/src/debug.rs index ba8353b73..18c5be4c5 100644 --- a/codegen/src/debug.rs +++ b/codegen/src/debug.rs @@ -1,5 +1,5 @@ use crate::cfg::{self, DocCfg}; -use crate::{file, lookup}; +use crate::{file, full, lookup}; use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; @@ -54,6 +54,7 @@ fn expand_impl_body( Data::Enum(variants) if variants.is_empty() => quote!(match *self {}), Data::Enum(variants) => { assert!(!is_syntax_tree_variant); + let mixed_derive_full = full::is_mixed_derive_full_enum(defs, node); let arms = variants.iter().map(|(variant_name, fields)| { let variant = Ident::new(variant_name, Span::call_site()); if fields.is_empty() { @@ -62,9 +63,10 @@ fn expand_impl_body( } } else { let mut cfg = None; - if node.ident == "Expr" { + if mixed_derive_full { if let Type::Syn(ty) = &fields[0] { - if !lookup::node(defs, ty).features.any.contains("derive") { + let features = &lookup::node(defs, ty).features; + if features.any.contains("full") && !features.any.contains("derive") { cfg = Some(quote!(#[cfg(feature = "full")])); } } @@ -89,7 +91,7 @@ fn expand_impl_body( } } }); - let nonexhaustive = if node.ident == "Expr" { + let nonexhaustive = if mixed_derive_full { Some(quote! { #[cfg(not(feature = "full"))] _ => unreachable!(), diff --git a/codegen/src/eq.rs b/codegen/src/eq.rs index cf08a9540..8effffd4c 100644 --- a/codegen/src/eq.rs +++ b/codegen/src/eq.rs @@ -1,4 +1,4 @@ -use crate::{cfg, file, lookup}; +use crate::{cfg, file, full, lookup}; use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; @@ -23,6 +23,7 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { match &node.data { Data::Enum(variants) if variants.is_empty() => quote!(match *self {}), Data::Enum(variants) => { + let mixed_derive_full = full::is_mixed_derive_full_enum(defs, node); let arms = variants.iter().map(|(variant_name, fields)| { let variant = Ident::new(variant_name, Span::call_site()); if fields.is_empty() { @@ -57,9 +58,10 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { comparisons.push(quote!(true)); } let mut cfg = None; - if node.ident == "Expr" { + if mixed_derive_full { if let Type::Syn(ty) = &fields[0] { - if !lookup::node(defs, ty).features.any.contains("derive") { + let features = &lookup::node(defs, ty).features; + if features.any.contains("full") && !features.any.contains("derive") { cfg = Some(quote!(#[cfg(feature = "full")])); } } diff --git a/codegen/src/full.rs b/codegen/src/full.rs index a41003189..eeeadd1e9 100644 --- a/codegen/src/full.rs +++ b/codegen/src/full.rs @@ -1,5 +1,7 @@ +use crate::lookup; use proc_macro2::TokenStream; use quote::quote; +use syn_codegen::{Data, Definitions, Node, Type}; pub fn get_macro() -> TokenStream { quote! { @@ -18,3 +20,71 @@ pub fn get_macro() -> TokenStream { } } } + +/// Syntax tree enum that has some variants enabled in "derive" mode and the +/// rest enabled in "full" mode. +pub fn is_mixed_derive_full_enum(defs: &Definitions, node: &Node) -> bool { + if !(node.features.any.contains("derive") && node.features.any.contains("full")) { + return false; + } + + let variants = match &node.data { + Data::Enum(variants) => variants, + Data::Private | Data::Struct(_) => return false, + }; + + let mut has_derive = false; + let mut has_full = false; + for fields in variants.values() { + match classify_variant(defs, fields) { + VariantAvailability::Derive => has_derive = true, + VariantAvailability::Full => has_full = true, + VariantAvailability::Other => {} + } + } + has_derive && has_full +} + +enum VariantAvailability { + Derive, + Full, + Other, +} + +fn classify_variant(defs: &Definitions, fields: &[Type]) -> VariantAvailability { + let mut has_derive = false; + let mut has_full = false; + for field in fields { + for_each_syn_type(field, &mut |ty| { + let node = lookup::node(defs, ty); + let derive = node.features.any.contains("derive"); + let full = node.features.any.contains("full"); + match (derive, full) { + (false, false) => {} + (false, true) => has_full = true, + (true, false | true) => has_derive = true, + } + }); + } + if has_full { + VariantAvailability::Full + } else if has_derive { + VariantAvailability::Derive + } else { + VariantAvailability::Other + } +} + +fn for_each_syn_type(ty: &Type, f: &mut dyn FnMut(&str)) { + match ty { + Type::Syn(ty) => f(ty), + Type::Std(_) | Type::Ext(_) | Type::Token(_) | Type::Group(_) => {} + Type::Punctuated(punctuated) => for_each_syn_type(&punctuated.element, f), + Type::Option(ty) | Type::Box(ty) | Type::Vec(ty) => for_each_syn_type(ty, f), + Type::Tuple(elements) => { + for ty in elements { + for_each_syn_type(ty, f); + } + } + } +} diff --git a/codegen/src/hash.rs b/codegen/src/hash.rs index fda5da278..2416d94e8 100644 --- a/codegen/src/hash.rs +++ b/codegen/src/hash.rs @@ -1,4 +1,4 @@ -use crate::{cfg, file, lookup}; +use crate::{cfg, file, full, lookup}; use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; @@ -23,6 +23,7 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { match &node.data { Data::Enum(variants) if variants.is_empty() => quote!(match *self {}), Data::Enum(variants) => { + let mixed_derive_full = full::is_mixed_derive_full_enum(defs, node); let arms = variants .iter() .enumerate() @@ -60,9 +61,11 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { pats.push(var); } let mut cfg = None; - if node.ident == "Expr" { + if mixed_derive_full { if let Type::Syn(ty) = &fields[0] { - if !lookup::node(defs, ty).features.any.contains("derive") { + let features = &lookup::node(defs, ty).features; + if features.any.contains("full") && !features.any.contains("derive") + { cfg = Some(quote!(#[cfg(feature = "full")])); } } @@ -76,7 +79,7 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { } } }); - let nonexhaustive = if node.ident == "Expr" { + let nonexhaustive = if mixed_derive_full { Some(quote! { #[cfg(not(feature = "full"))] _ => unreachable!(),