From dbcb9f5ad1e4801f6c3e514c399eec8e92603fa5 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Sat, 16 Nov 2024 21:22:28 +0100 Subject: [PATCH 1/6] Rename attr utility types and functions --- macros/src/utils.rs | 61 ++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/macros/src/utils.rs b/macros/src/utils.rs index c0f7654..da8b516 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -6,22 +6,22 @@ use syn::{ }; #[derive(Default)] -pub(crate) struct ExcludeAttr {} +pub(crate) struct VariantExcludeAttr {} #[derive(Default)] -pub(crate) struct IncludeFieldAttr { +pub(crate) struct VariantIncludeFieldAttr { pub index: usize, } #[derive(Default)] -pub(crate) struct IncludeAttr { - pub field: IncludeFieldAttr, +pub(crate) struct VariantIncludeAttr { + pub field: VariantIncludeFieldAttr, } #[derive(Default)] pub(crate) struct VariantAttrs { - pub exclude: Option, - pub include: Option, + pub exclude: Option, + pub include: Option, } pub(crate) struct FieldInfo<'a> { @@ -40,15 +40,18 @@ const EXCLUDE_ATTR: &str = "exclude"; const INCLUDE_ATTR: &str = "include"; const FIELD_ATTR: &str = "field"; -fn ident(variant: &Variant) -> Result { +fn variant_ident(variant: &Variant) -> Result { Ok(variant.ident.clone()) } -fn exclude_attr(_variant: &Variant, _meta: &ParseNestedMeta) -> Result { - Ok(ExcludeAttr {}) +fn variant_exclude_attr( + _variant: &Variant, + _meta: &ParseNestedMeta, +) -> Result { + Ok(VariantExcludeAttr::default()) } -fn index_of_field_with_name( +fn index_of_variant_field_with_name( variant: &Variant, name: &str, meta: &MetaNameValue, @@ -76,7 +79,7 @@ fn index_of_field_with_name( } } -fn index_of_field_with_index( +fn index_of_variant_field_with_index( variant: &Variant, index: usize, meta: &MetaNameValue, @@ -101,16 +104,19 @@ fn index_of_field_with_index( } } -fn include_field_attr(variant: &Variant, meta: &MetaNameValue) -> Result { +fn variant_include_field_attr( + variant: &Variant, + meta: &MetaNameValue, +) -> Result { match &meta.value { Expr::Lit(expr_lit) => match &expr_lit.lit { Lit::Str(lit) => { - let index = index_of_field_with_name(variant, &lit.value(), meta)?; - Ok(IncludeFieldAttr { index }) + let index = index_of_variant_field_with_name(variant, &lit.value(), meta)?; + Ok(VariantIncludeFieldAttr { index }) } Lit::Int(lit) => { - let index = index_of_field_with_index(variant, lit.base10_parse()?, meta)?; - Ok(IncludeFieldAttr { index }) + let index = index_of_variant_field_with_index(variant, lit.base10_parse()?, meta)?; + Ok(VariantIncludeFieldAttr { index }) } _ => Err(Error::new_spanned( meta, @@ -121,8 +127,11 @@ fn include_field_attr(variant: &Variant, meta: &MetaNameValue) -> Result Result { - let mut attr = IncludeAttr::default(); +fn variant_include_attr( + variant: &Variant, + meta: &ParseNestedMeta, +) -> Result { + let mut attr = VariantIncludeAttr::default(); let content; syn::parenthesized!(content in meta.input); @@ -134,7 +143,7 @@ fn include_attr(variant: &Variant, meta: &ParseNestedMeta) -> Result return Err(Error::new_spanned(meta, "not supported!")), Meta::NameValue(name_value) => { if name_value.path.is_ident(FIELD_ATTR) { - attr.field = include_field_attr(variant, name_value)?; + attr.field = variant_include_field_attr(variant, name_value)?; } else { return Err(Error::new_spanned(meta, "not supported!")); } @@ -145,7 +154,7 @@ fn include_attr(variant: &Variant, meta: &ParseNestedMeta) -> Result Result { +fn variant_attrs(variant: &Variant) -> Result { let mut attrs = VariantAttrs::default(); for attr in &variant.attrs { @@ -155,10 +164,10 @@ fn attrs(variant: &Variant) -> Result { attr.parse_nested_meta(|meta| { if meta.path.is_ident(EXCLUDE_ATTR) { - attrs.exclude = Some(exclude_attr(variant, &meta)?); + attrs.exclude = Some(variant_exclude_attr(variant, &meta)?); Ok(()) } else if meta.path.is_ident(INCLUDE_ATTR) { - attrs.include = Some(include_attr(variant, &meta)?); + attrs.include = Some(variant_include_attr(variant, &meta)?); Ok(()) } else { Err(meta.error("unsupported attribute")) @@ -169,7 +178,7 @@ fn attrs(variant: &Variant) -> Result { Ok(attrs) } -fn fields(variant: &Variant) -> Result, Error> { +fn variant_fields(variant: &Variant) -> Result, Error> { let fields = match &variant.fields { Fields::Named(fields) => Vec::from_iter(fields.named.iter()), Fields::Unnamed(fields) => Vec::from_iter(fields.unnamed.iter()), @@ -193,9 +202,9 @@ where for variant in variants { info.push(VariantInfo { - ident: ident(variant)?, - attrs: attrs(variant)?, - fields: fields(variant)?, + ident: variant_ident(variant)?, + attrs: variant_attrs(variant)?, + fields: variant_fields(variant)?, }); } From 148f8449d8357534cce5b6c63140c1935aefe852 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 18 Nov 2024 12:19:23 +0100 Subject: [PATCH 2/6] Refactor `enumcapsulate-macros` crate and extend helper attrs --- macros/src/config.rs | 333 ++++++++++++++++++++ macros/src/enum_deriver.rs | 605 ++++++++++++++++++++----------------- macros/src/lib.rs | 7 +- macros/src/utils.rs | 247 ++++----------- 4 files changed, 721 insertions(+), 471 deletions(-) create mode 100644 macros/src/config.rs diff --git a/macros/src/config.rs b/macros/src/config.rs new file mode 100644 index 0000000..8bed651 --- /dev/null +++ b/macros/src/config.rs @@ -0,0 +1,333 @@ +use syn::{parse::Parse, punctuated::Punctuated}; + +use crate::{attr, macro_name}; + +static RECOGNIZED_ENUM_LEVEL_MACROS: &[&str] = &[ + macro_name::FROM, + macro_name::TRY_INTO, + macro_name::FROM_VARIANT, + macro_name::INTO_VARIANT, + macro_name::AS_VARIANT, + macro_name::AS_VARIANT_REF, + macro_name::AS_VARIANT_MUT, + macro_name::IS_VARIANT, + macro_name::VARIANT_DISCRIMINANT, + macro_name::VARIANT_DOWNCAST, +]; + +static RECOGNIZED_VARIANT_LEVEL_MACROS: &[&str] = &[ + macro_name::FROM, + macro_name::TRY_INTO, + macro_name::FROM_VARIANT, + macro_name::INTO_VARIANT, + macro_name::AS_VARIANT, + macro_name::AS_VARIANT_REF, + macro_name::AS_VARIANT_MUT, +]; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub(crate) enum VariantFieldConfig { + Name(String), + Index(usize), +} + +#[derive(Clone, Default, Debug)] +pub(crate) struct EnumConfig { + // #[enumcapsulate(exclude(…))] + pub exclude: Option>, +} + +impl EnumConfig { + pub fn is_excluded(&self, name: &str) -> bool { + if let Some(excluded) = &self.exclude { + return excluded.iter().any(|ident| ident == name); + } else { + false + } + } +} + +#[derive(Clone, Default, Debug)] +pub(crate) struct VariantConfig { + // #[enumcapsulate(exclude(…))] + pub exclude: Option>, + + // #[enumcapsulate(include(…))] + pub include: Option>, + + // #[enumcapsulate(field(… = …))] + pub field: Option, +} + +impl VariantConfig { + pub fn is_excluded(&self, name: &str, config: &EnumConfig) -> bool { + if self.is_excluded_explicitly(name) { + assert!(!self.is_included_explicitly(name)); + return true; + } + + if self.is_included_explicitly(name) { + return false; + } + + config.is_excluded(name) + } + + pub fn is_excluded_explicitly(&self, name: &str) -> bool { + if let Some(excluded) = &self.exclude { + if excluded.is_empty() { + if let Some(included) = &self.include { + return !included.iter().any(|ident| ident == name); + } else { + return true; + } + } + + excluded.iter().any(|ident| ident == name) + } else { + false + } + } + + pub fn is_included_explicitly(&self, name: &str) -> bool { + if let Some(included) = &self.include { + if included.is_empty() { + if let Some(excluded) = &self.exclude { + return !excluded.iter().any(|ident| ident == name); + } else { + return true; + } + } + + included.iter().any(|ident| ident == name) + } else { + false + } + } +} + +pub(crate) fn config_for_enum_with_attrs( + enum_attrs: &[syn::Attribute], +) -> Result { + let mut config = EnumConfig::default(); + + parse_enumcapsulate_attrs(enum_attrs, |meta| { + if meta.path.is_ident(attr::EXCLUDE) { + // #[enumcapsulate(exclude(…))] + + let mut exclude = config.exclude.take().unwrap_or_default(); + exclude.extend(macro_idents_for_enum(&meta)?.into_iter()); + config.exclude = Some(exclude); + } else { + return Err(meta.error("unrecognized attribute")); + } + + Ok(()) + })?; + + Ok(config) +} + +pub(crate) fn config_for_variant(variant: &syn::Variant) -> Result { + let mut config = VariantConfig::default(); + + let fields = match &variant.fields { + syn::Fields::Named(fields) => fields.named.iter().collect(), + syn::Fields::Unnamed(fields) => fields.unnamed.iter().collect(), + syn::Fields::Unit => vec![], + }; + + parse_enumcapsulate_attrs(&variant.attrs, |meta| { + if meta.path.is_ident(attr::EXCLUDE) { + // #[enumcapsulate(exclude(…))] + + let mut exclude = config.exclude.take().unwrap_or_default(); + let conflicting = config.include.as_deref().unwrap_or(&[]); + + exclude.extend(macro_idents_for_variant(&meta, conflicting)?.into_iter()); + config.exclude = Some(exclude); + } else if meta.path.is_ident(attr::INCLUDE) { + // #[enumcapsulate(include(…))] + + let mut include = config.include.take().unwrap_or_default(); + let conflicting = config.exclude.as_deref().unwrap_or(&[]); + + include.extend(macro_idents_for_variant(&meta, conflicting)?.into_iter()); + config.include = Some(include); + } else if meta.path.is_ident(attr::FIELD) { + // #[enumcapsulate(field(…))] + meta.parse_nested_meta(|meta| { + if meta.path.is_ident(attr::NAME) { + // #[enumcapsulate(field(name = "…"))] + + if !matches!(&variant.fields, syn::Fields::Named(_)) { + return Err(meta.error("no named fields in variant")); + } + + let lit: syn::LitStr = meta.value()?.parse()?; + let name = lit.value(); + + let field_idents: Vec<_> = fields + .iter() + .filter_map(|&field| field.ident.as_ref()) + .collect(); + + if field_idents.is_empty() { + return Err(meta.error("no named fields in variant")); + } + + let field_exists = field_idents.into_iter().any(|ident| ident == &name); + + if !field_exists { + return Err(meta.error("field not found in variant")); + } + + config.field = Some(VariantFieldConfig::Name(name)); + + Ok(()) + } else if meta.path.is_ident(attr::INDEX) { + // #[enumcapsulate(field(index = …))] + + if fields.is_empty() { + return Err(meta.error("no fields in variant")); + } + + let lit: syn::LitInt = meta.value()?.parse()?; + let index = lit.base10_parse()?; + + if fields.len() <= index { + return Err(meta.error("field index out of bounds")); + } + + config.field = Some(VariantFieldConfig::Index(index)); + + Ok(()) + } else { + return Err(meta.error("unrecognized attribute")); + } + })?; + } else { + return Err(meta.error("unrecognized attribute")); + } + + Ok(()) + })?; + + Ok(config) +} + +pub(crate) fn parse_enumcapsulate_attrs( + attrs: &[syn::Attribute], + logic: impl FnMut(syn::meta::ParseNestedMeta) -> Result<(), syn::Error>, +) -> Result<(), syn::Error> { + let mut logic = logic; + + for attr in attrs { + if !attr.path().is_ident(attr::NAMESPACE) { + continue; + } + + // #[enumcapsulate(…)] + attr.parse_nested_meta(&mut logic)?; + } + + Ok(()) +} + +pub(crate) fn macro_idents_for_enum( + meta: &syn::meta::ParseNestedMeta<'_>, +) -> Result, syn::Error> { + let idents = parse_idents_from_meta_list(meta)?; + + let recognized = RECOGNIZED_ENUM_LEVEL_MACROS; + ensure_only_recognized_ident_names(&idents, recognized)?; + + Ok(idents) +} + +pub(crate) fn macro_idents_for_variant( + meta: &syn::meta::ParseNestedMeta<'_>, + conflict_list: &[syn::Ident], +) -> Result, syn::Error> { + let idents = parse_idents_from_meta_list(meta)?; + + let recognized = RECOGNIZED_VARIANT_LEVEL_MACROS; + ensure_only_recognized_ident_names(&idents, recognized)?; + + ensure_no_conflicting_idents(&idents, conflict_list)?; + + Ok(idents) +} + +pub(crate) fn ensure_only_recognized_ident_names( + idents: &[syn::Ident], + recognized: &[&str], +) -> Result<(), syn::Error> { + let mut error: Option = None; + + let unrecognized = idents + .iter() + .filter(|&ident| !recognized.iter().any(|recognized| ident == recognized)); + + for ident in unrecognized { + let ident_err = syn::Error::new_spanned(ident, "unrecognized macro derive"); + if let Some(error) = error.as_mut() { + error.combine(ident_err); + } else { + error = Some(ident_err) + } + } + + if let Some(err) = error { + return Err(err); + } + + Ok(()) +} + +pub(crate) fn ensure_no_conflicting_idents( + idents: &[syn::Ident], + conflicting: &[syn::Ident], +) -> Result<(), syn::Error> { + let mut error: Option = None; + + let conflicting = idents + .iter() + .filter(|&ident| conflicting.iter().any(|conflicting| ident == conflicting)); + + for ident in conflicting { + let ident_err = syn::Error::new_spanned(ident, "conflicting macro derive"); + if let Some(error) = error.as_mut() { + error.combine(ident_err); + } else { + error = Some(ident_err) + } + } + + if let Some(err) = error { + return Err(err); + } + + Ok(()) +} + +pub(crate) fn parse_idents_from_meta_list( + meta: &syn::meta::ParseNestedMeta<'_>, +) -> Result, syn::Error> { + let mut idents = vec![]; + + let lookahead = meta.input.lookahead1(); + if lookahead.peek(syn::token::Paren) { + let content; + syn::parenthesized!(content in meta.input); + let punctuated: Punctuated = + content.parse_terminated(syn::Ident::parse, syn::Token![,])?; + + idents.extend(punctuated); + } + + Ok(idents) +} + +#[cfg(test)] +mod tests; diff --git a/macros/src/enum_deriver.rs b/macros/src/enum_deriver.rs index 860d34a..6136de1 100644 --- a/macros/src/enum_deriver.rs +++ b/macros/src/enum_deriver.rs @@ -2,7 +2,9 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{parse_quote_spanned, DataEnum, DeriveInput, Fields, Type, Variant}; -use crate::utils::{self, FieldInfo, VariantInfo}; +use crate::{ + config_for_enum_with_attrs, config_for_variant, macro_name, position_of_selected_field, +}; pub(crate) struct EnumDeriver { input: DeriveInput, @@ -27,42 +29,66 @@ impl EnumDeriver { } pub fn derive_from(&self) -> Result { - let outer = &self.input.ident; - let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + const DERIVE_MACRO_NAME: &str = macro_name::FROM; - let variants = self.variants()?; - let variant_infos: Vec = utils::variant_infos(variants)?; + let derive_input = &self.input; + let enum_ident = &derive_input.ident; + + let enum_config = config_for_enum_with_attrs(&self.input.attrs)?; + + if enum_config.is_excluded(DERIVE_MACRO_NAME) { + return Ok(TokenStream2::default()); + } + + let outer = enum_ident; + let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; let mut impls: Vec = vec![]; - for variant_info in variant_infos { - let VariantInfo { - ident: inner, - attrs, - fields, - } = variant_info; + for variant in self.variants()? { + let variant_ident = &variant.ident; + let inner = variant_ident; - if attrs.exclude.is_some() { + let variant_config = config_for_variant(variant)?; + + if variant_config.is_excluded(DERIVE_MACRO_NAME, &enum_config) { continue; } - let field_info = if let [field_info] = &fields[..] { - Some(field_info) - } else { - None - }; - - let Some(FieldInfo { - ident: inner_field, - ty: inner_ty, - }) = field_info + let Some(selection_index) = + position_of_selected_field(&variant.fields, variant_config.field.as_ref())? else { continue; }; - let expression = match inner_field { - Some(inner_field) => quote! { Self::#inner { #inner_field: inner} }, - None => quote! { Self::#inner(inner) }, + let fields: Vec<_> = variant.fields.iter().collect(); + let inner_field = fields[selection_index]; + let inner_ty = &inner_field.ty; + + let field_expressions: Vec<_> = fields + .iter() + .enumerate() + .map(|(field_index, &field)| { + let expr = if field_index == selection_index { + quote! { inner } + } else { + quote! { Default::default() } + }; + match &field.ident { + Some(field) => quote! { #field: #expr }, + None => quote! { #expr }, + } + }) + .collect(); + + let expression = match &variant.fields { + Fields::Named(_) => { + quote! { Self::#inner { #(#field_expressions),* } } + } + Fields::Unnamed(_) => { + quote! { Self::#inner(#(#field_expressions),*) } + } + Fields::Unit => continue, }; impls.push(quote! { @@ -80,55 +106,51 @@ impl EnumDeriver { } pub fn derive_try_into(&self) -> Result { - let outer = &self.input.ident; - let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + const DERIVE_MACRO_NAME: &str = macro_name::TRY_INTO; - let variants = self.variants()?; - let variant_infos: Vec = utils::variant_infos(variants)?; + let derive_input = &self.input; + let enum_ident = &derive_input.ident; + + let enum_config = config_for_enum_with_attrs(&self.input.attrs)?; + + let outer = enum_ident; + let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; let mut impls: Vec = vec![]; - for variant_info in variant_infos { - let VariantInfo { - ident: inner, - attrs, - fields, - } = variant_info; + for variant in self.variants()? { + let variant_ident = &variant.ident; + let inner = variant_ident; + + let variant_config = config_for_variant(variant)?; - if attrs.exclude.is_some() { + if variant_config.is_excluded(DERIVE_MACRO_NAME, &enum_config) { continue; } - let field_info = if let Some(include_info) = &attrs.include { - let index = include_info.field.index; - Some((index, &fields[index])) - } else if let [field_info] = &fields[..] { - Some((0, field_info)) - } else { - None - }; - - let Some(( - field_index, - FieldInfo { - ident: inner_field, - ty: inner_ty, - }, - )) = field_info + let Some(selection_index) = + position_of_selected_field(&variant.fields, variant_config.field.as_ref())? else { continue; }; - let pattern = match inner_field { - Some(inner_field) => quote! { - #outer_ty::#inner { #inner_field: inner, .. } - }, - None => { - let underscores = (0..field_index).map(|_| quote! { _, }); - quote! { - #outer_ty::#inner(#(#underscores)* inner, ..) - } + let fields: Vec<_> = variant.fields.iter().collect(); + let inner_field = fields[selection_index]; + let inner_ident = inner_field.ident.as_ref(); + let inner_ty = &inner_field.ty; + + let pattern = match &variant.fields { + Fields::Named(_) => { + let field = inner_ident; + quote! { #outer_ty::#inner { #field: inner, .. } } } + Fields::Unnamed(_) => { + let underscores = (0..selection_index).map(|_| { + quote! { _, } + }); + quote! { #outer_ty::#inner(#(#underscores)* inner, ..) } + } + Fields::Unit => continue, }; impls.push(quote! { @@ -151,42 +173,62 @@ impl EnumDeriver { } pub fn derive_from_variant(&self) -> Result { - let outer = &self.input.ident; - let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + const DERIVE_MACRO_NAME: &str = macro_name::FROM_VARIANT; - let variants = self.variants()?; - let variant_infos: Vec = utils::variant_infos(variants)?; + let derive_input = &self.input; + let enum_ident = &derive_input.ident; + + let enum_config = config_for_enum_with_attrs(&self.input.attrs)?; + + let outer = enum_ident; + let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; let mut impls: Vec = vec![]; - for variant_info in variant_infos { - let VariantInfo { - ident: inner, - attrs, - fields, - } = variant_info; + for variant in self.variants()? { + let variant_ident = &variant.ident; + let inner = variant_ident; + + let variant_config = config_for_variant(variant)?; - if attrs.exclude.is_some() { + if variant_config.is_excluded(DERIVE_MACRO_NAME, &enum_config) { continue; } - let field_info = if let [field_info] = &fields[..] { - Some(field_info) - } else { - None - }; - - let Some(FieldInfo { - ident: inner_field, - ty: inner_ty, - }) = field_info + let Some(selection_index) = + position_of_selected_field(&variant.fields, variant_config.field.as_ref())? else { continue; }; - let expression = match inner_field { - Some(inner_field) => quote! { Self::#inner { #inner_field: inner} }, - None => quote! { Self::#inner(inner) }, + let fields: Vec<_> = variant.fields.iter().collect(); + let inner_field = fields[selection_index]; + let inner_ty = &inner_field.ty; + + let field_expressions: Vec<_> = fields + .iter() + .enumerate() + .map(|(field_index, &field)| { + let expr = if field_index == selection_index { + quote! { inner } + } else { + quote! { Default::default() } + }; + match &field.ident { + Some(field) => quote! { #field: #expr }, + None => quote! { #expr }, + } + }) + .collect(); + + let expression = match &variant.fields { + Fields::Named(_) => { + quote! { Self::#inner { #(#field_expressions),* } } + } + Fields::Unnamed(_) => { + quote! { Self::#inner(#(#field_expressions),*) } + } + Fields::Unit => continue, }; impls.push(quote! { @@ -204,59 +246,58 @@ impl EnumDeriver { } pub fn derive_as_variant(&self) -> Result { - let outer = &self.input.ident; - let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + const DERIVE_MACRO_NAME: &str = macro_name::AS_VARIANT; - let variants = self.variants()?; - let variant_infos: Vec = utils::variant_infos(variants)?; + let derive_input = &self.input; + let enum_ident = &derive_input.ident; + + let enum_config = config_for_enum_with_attrs(&self.input.attrs)?; + + let outer = enum_ident; + let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; let mut impls: Vec = vec![]; - for variant_info in variant_infos { - let VariantInfo { - ident: inner, - attrs, - fields, - } = variant_info; + for variant in self.variants()? { + let variant_ident = &variant.ident; + let inner = variant_ident; + + let variant_config = config_for_variant(variant)?; - if attrs.exclude.is_some() { + if variant_config.is_excluded(DERIVE_MACRO_NAME, &enum_config) { continue; } - let field_info = if let Some(include_info) = &attrs.include { - let index = include_info.field.index; - Some((index, &fields[index])) - } else if let [field_info] = &fields[..] { - Some((0, field_info)) - } else { - None - }; - - let Some(( - field_index, - FieldInfo { - ident: inner_field, - ty: inner_ty, - }, - )) = field_info + let Some(selection_index) = + position_of_selected_field(&variant.fields, variant_config.field.as_ref())? else { continue; }; - let pattern = match inner_field { - Some(inner_field) => quote! { - #outer_ty::#inner { #inner_field: inner, .. } - }, - None => { - let underscores = (0..field_index).map(|_| quote! { _, }); - quote! { - #outer_ty::#inner(#(#underscores)* inner, ..) - } + let fields: Vec<_> = variant.fields.iter().collect(); + let inner_field = fields[selection_index]; + let inner_ident = inner_field.ident.as_ref(); + let inner_ty = &inner_field.ty; + + let pattern = match &variant.fields { + Fields::Named(_) => { + let field = inner_ident; + quote! { #outer_ty::#inner { #field: inner, .. } } } + Fields::Unnamed(_) => { + let underscores = (0..selection_index).map(|_| { + quote! { _, } + }); + quote! { #outer_ty::#inner(#(#underscores)* inner, ..) } + } + Fields::Unit => continue, }; impls.push(quote! { - impl ::enumcapsulate::AsVariant<#inner_ty> for #outer_ty where #inner_ty: Clone { + impl ::enumcapsulate::AsVariant<#inner_ty> for #outer_ty + where + #inner_ty: Clone + { fn as_variant(&self) -> Option<#inner_ty> { match self { #pattern => Some(inner.clone()), @@ -273,55 +314,51 @@ impl EnumDeriver { } pub fn derive_as_variant_ref(&self) -> Result { - let outer = &self.input.ident; - let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + const DERIVE_MACRO_NAME: &str = macro_name::AS_VARIANT_REF; - let variants = self.variants()?; - let variant_infos: Vec = utils::variant_infos(variants)?; + let derive_input = &self.input; + let enum_ident = &derive_input.ident; + + let enum_config = config_for_enum_with_attrs(&derive_input.attrs)?; + + let outer = enum_ident; + let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; let mut impls: Vec = vec![]; - for variant_info in variant_infos { - let VariantInfo { - ident: inner, - attrs, - fields, - } = variant_info; + for variant in self.variants()? { + let variant_ident = &variant.ident; + let inner = variant_ident; + + let variant_config = config_for_variant(variant)?; - if attrs.exclude.is_some() { + if variant_config.is_excluded(DERIVE_MACRO_NAME, &enum_config) { continue; } - let field_info = if let Some(include_info) = &attrs.include { - let index = include_info.field.index; - Some((index, &fields[index])) - } else if let [field_info] = &fields[..] { - Some((0, field_info)) - } else { - None - }; - - let Some(( - field_index, - FieldInfo { - ident: inner_field, - ty: inner_ty, - }, - )) = field_info + let Some(selection_index) = + position_of_selected_field(&variant.fields, variant_config.field.as_ref())? else { continue; }; - let pattern = match inner_field { - Some(inner_field) => quote! { - #outer_ty::#inner { #inner_field: inner, .. } - }, - None => { - let underscores = (0..field_index).map(|_| quote! { _, }); - quote! { - #outer_ty::#inner(#(#underscores)* inner, ..) - } + let fields: Vec<_> = variant.fields.iter().collect(); + let inner_field = fields[selection_index]; + let inner_ident = inner_field.ident.as_ref(); + let inner_ty = &inner_field.ty; + + let pattern = match &variant.fields { + Fields::Named(_) => { + let field = inner_ident; + quote! { #outer_ty::#inner { #field: inner, .. } } + } + Fields::Unnamed(_) => { + let underscores = (0..selection_index).map(|_| { + quote! { _, } + }); + quote! { #outer_ty::#inner(#(#underscores)* inner, ..) } } + Fields::Unit => continue, }; impls.push(quote! { @@ -342,55 +379,51 @@ impl EnumDeriver { } pub fn derive_as_variant_mut(&self) -> Result { - let outer = &self.input.ident; - let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + const DERIVE_MACRO_NAME: &str = macro_name::AS_VARIANT_MUT; - let variants = self.variants()?; - let variant_infos: Vec = utils::variant_infos(variants)?; + let derive_input = &self.input; + let enum_ident = &derive_input.ident; + + let enum_config = config_for_enum_with_attrs(&self.input.attrs)?; + + let outer = enum_ident; + let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; let mut impls: Vec = vec![]; - for variant_info in variant_infos { - let VariantInfo { - ident: inner, - attrs, - fields, - } = variant_info; + for variant in self.variants()? { + let variant_ident = &variant.ident; + let inner = variant_ident; + + let variant_config = config_for_variant(variant)?; - if attrs.exclude.is_some() { + if variant_config.is_excluded(DERIVE_MACRO_NAME, &enum_config) { continue; } - let field_info = if let Some(include_info) = &attrs.include { - let index = include_info.field.index; - Some((index, &fields[index])) - } else if let [field_info] = &fields[..] { - Some((0, field_info)) - } else { - None - }; - - let Some(( - field_index, - FieldInfo { - ident: inner_field, - ty: inner_ty, - }, - )) = field_info + let Some(selection_index) = + position_of_selected_field(&variant.fields, variant_config.field.as_ref())? else { continue; }; - let pattern = match inner_field { - Some(inner_field) => quote! { - #outer_ty::#inner { #inner_field: inner, .. } - }, - None => { - let underscores = (0..field_index).map(|_| quote! { _, }); - quote! { - #outer_ty::#inner(#(#underscores)* inner, ..) - } + let fields: Vec<_> = variant.fields.iter().collect(); + let inner_field = fields[selection_index]; + let inner_ident = inner_field.ident.as_ref(); + let inner_ty = &inner_field.ty; + + let pattern = match &variant.fields { + Fields::Named(_) => { + let field = inner_ident; + quote! { #outer_ty::#inner { #field: inner, .. } } + } + Fields::Unnamed(_) => { + let underscores = (0..selection_index).map(|_| { + quote! { _, } + }); + quote! { #outer_ty::#inner(#(#underscores)* inner, ..) } } + Fields::Unit => continue, }; impls.push(quote! { @@ -411,55 +444,51 @@ impl EnumDeriver { } pub fn derive_into_variant(&self) -> Result { - let outer = &self.input.ident; - let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + const DERIVE_MACRO_NAME: &str = macro_name::INTO_VARIANT; - let variants = self.variants()?; - let variant_infos: Vec = utils::variant_infos(variants)?; + let derive_input = &self.input; + let enum_ident = &derive_input.ident; + + let enum_config = config_for_enum_with_attrs(&self.input.attrs)?; + + let outer = enum_ident; + let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; let mut impls: Vec = vec![]; - for variant_info in variant_infos { - let VariantInfo { - ident: inner, - attrs, - fields, - } = variant_info; + for variant in self.variants()? { + let variant_ident = &variant.ident; + let inner = variant_ident; + + let variant_config = config_for_variant(variant)?; - if attrs.exclude.is_some() { + if variant_config.is_excluded(DERIVE_MACRO_NAME, &enum_config) { continue; } - let field_info = if let Some(include_info) = &attrs.include { - let index = include_info.field.index; - Some((index, &fields[index])) - } else if let [field_info] = &fields[..] { - Some((0, field_info)) - } else { - None - }; - - let Some(( - field_index, - FieldInfo { - ident: inner_field, - ty: inner_ty, - }, - )) = field_info + let Some(selection_index) = + position_of_selected_field(&variant.fields, variant_config.field.as_ref())? else { continue; }; - let pattern = match inner_field { - Some(inner_field) => quote! { - #outer_ty::#inner { #inner_field: inner, .. } - }, - None => { - let underscores = (0..field_index).map(|_| quote! { _, }); - quote! { - #outer_ty::#inner(#(#underscores)* inner, ..) - } + let fields: Vec<_> = variant.fields.iter().collect(); + let inner_field = fields[selection_index]; + let inner_ident = inner_field.ident.as_ref(); + let inner_ty = &inner_field.ty; + + let pattern = match &variant.fields { + Fields::Named(_) => { + let field = inner_ident; + quote! { #outer_ty::#inner { #field: inner, .. } } + } + Fields::Unnamed(_) => { + let underscores = (0..selection_index).map(|_| { + quote! { _, } + }); + quote! { #outer_ty::#inner(#(#underscores)* inner, ..) } } + Fields::Unit => continue, }; impls.push(quote! { @@ -479,67 +508,55 @@ impl EnumDeriver { }) } - pub fn derive_variant_downcast(&self) -> Result { - let outer = &self.input.ident; - let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + pub fn derive_is_variant(&self) -> Result { + const DERIVE_MACRO_NAME: &str = macro_name::AS_VARIANT_REF; - let tokens = quote! { - impl ::enumcapsulate::VariantDowncast for #outer_ty {} - }; + let derive_input = &self.input; + let enum_ident = &derive_input.ident; - Ok(tokens) - } + let enum_config = config_for_enum_with_attrs(&self.input.attrs)?; - pub fn derive_is_variant(&self) -> Result { - let outer = &self.input.ident; - let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + if enum_config.is_excluded(DERIVE_MACRO_NAME) { + return Ok(TokenStream2::default()); + } - let variants = self.variants()?; - let variant_infos: Vec = utils::variant_infos(variants)?; + let outer = enum_ident; + let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; let mut match_arms: Vec = vec![]; - for variant_info in variant_infos { - let VariantInfo { - ident: inner, - attrs, - fields, - } = variant_info; + for variant in self.variants()? { + let variant_ident = &variant.ident; + let inner = variant_ident; + + let variant_config = config_for_variant(variant)?; - if attrs.exclude.is_some() { + if variant_config.is_excluded(DERIVE_MACRO_NAME, &enum_config) { continue; } - let field_info = if let Some(include_info) = &attrs.include { - let index = include_info.field.index; - Some((index, &fields[index])) - } else if let [field_info] = &fields[..] { - Some((0, field_info)) - } else { - None - }; - - let Some(( - field_index, - FieldInfo { - ident: inner_field, - ty: _, - }, - )) = field_info + let Some(selection_index) = + position_of_selected_field(&variant.fields, variant_config.field.as_ref())? else { continue; }; - let pattern = match inner_field { - Some(inner_field) => quote! { - #outer_ty::#inner { #inner_field: inner, .. } - }, - None => { - let underscores = (0..field_index).map(|_| quote! { _, }); - quote! { - #outer_ty::#inner(#(#underscores)* inner, ..) - } + let fields: Vec<_> = variant.fields.iter().collect(); + let inner_field = fields[selection_index]; + let inner_ident = inner_field.ident.as_ref(); + + let pattern = match &variant.fields { + Fields::Named(_) => { + let field = inner_ident; + quote! { #outer_ty::#inner { #field: inner, .. } } } + Fields::Unnamed(_) => { + let underscores = (0..selection_index).map(|_| { + quote! { _, } + }); + quote! { #outer_ty::#inner(#(#underscores)* inner, ..) } + } + Fields::Unit => continue, }; match_arms.push(quote! { @@ -571,8 +588,41 @@ impl EnumDeriver { }) } + pub fn derive_variant_downcast(&self) -> Result { + const DERIVE_MACRO_NAME: &str = macro_name::VARIANT_DOWNCAST; + + let derive_input = &self.input; + let enum_ident = &derive_input.ident; + + let enum_config = config_for_enum_with_attrs(&self.input.attrs)?; + + if enum_config.is_excluded(DERIVE_MACRO_NAME) { + return Ok(TokenStream2::default()); + } + + let outer = enum_ident; + let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; + + let tokens = quote! { + impl ::enumcapsulate::VariantDowncast for #outer_ty {} + }; + + Ok(tokens) + } + pub fn derive_variant_discriminant(&self) -> Result { - let outer = &self.input.ident; + const DERIVE_MACRO_NAME: &str = macro_name::VARIANT_DISCRIMINANT; + + let derive_input = &self.input; + let enum_ident = &derive_input.ident; + + let enum_config = config_for_enum_with_attrs(&self.input.attrs)?; + + if enum_config.is_excluded(DERIVE_MACRO_NAME) { + return Ok(TokenStream2::default()); + } + + let outer = enum_ident; let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer }; let variants = self.variants()?; @@ -599,7 +649,8 @@ impl EnumDeriver { let mut match_arms: Vec = vec![]; for variant in variants { - let inner = &variant.ident; + let variant_ident = &variant.ident; + let inner = variant_ident; let pattern = match &variant.fields { Fields::Named(_) => quote! { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 40bc70a..02c9c1e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -3,11 +3,14 @@ use syn::{parse_macro_input, DeriveInput}; use crate::utils::tokenstream; -use self::enum_deriver::EnumDeriver; - +mod config; mod enum_deriver; mod utils; +use config::*; +use enum_deriver::*; +use utils::*; + /// Derive macro generating an impl of the trait `From`. /// /// It generates an impl for each of the enum's diff --git a/macros/src/utils.rs b/macros/src/utils.rs index da8b516..15d2e7a 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -1,214 +1,77 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use syn::{ - meta::ParseNestedMeta, parse::Parse, Error, Expr, Fields, Ident, Lit, Meta, MetaNameValue, - Token, Type, Variant, -}; -#[derive(Default)] -pub(crate) struct VariantExcludeAttr {} +use crate::config::VariantFieldConfig; -#[derive(Default)] -pub(crate) struct VariantIncludeFieldAttr { - pub index: usize, +pub(crate) mod attr { + pub(crate) const NAMESPACE: &str = "enumcapsulate"; + pub(crate) const EXCLUDE: &str = "exclude"; + pub(crate) const INCLUDE: &str = "include"; + pub(crate) const FIELD: &str = "field"; + pub(crate) const NAME: &str = "name"; + pub(crate) const INDEX: &str = "index"; } -#[derive(Default)] -pub(crate) struct VariantIncludeAttr { - pub field: VariantIncludeFieldAttr, -} - -#[derive(Default)] -pub(crate) struct VariantAttrs { - pub exclude: Option, - pub include: Option, -} - -pub(crate) struct FieldInfo<'a> { - pub ident: Option, - pub ty: &'a Type, -} +pub(crate) mod macro_name { + pub(crate) const FROM: &str = "From"; + pub(crate) const TRY_INTO: &str = "TryInto"; -pub(crate) struct VariantInfo<'a> { - pub ident: Ident, - pub attrs: VariantAttrs, - pub fields: Vec>, -} - -const NAMESPACE_ATTR: &str = "enumcapsulate"; -const EXCLUDE_ATTR: &str = "exclude"; -const INCLUDE_ATTR: &str = "include"; -const FIELD_ATTR: &str = "field"; + pub(crate) const FROM_VARIANT: &str = "FromVariant"; + pub(crate) const INTO_VARIANT: &str = "IntoVariant"; -fn variant_ident(variant: &Variant) -> Result { - Ok(variant.ident.clone()) -} - -fn variant_exclude_attr( - _variant: &Variant, - _meta: &ParseNestedMeta, -) -> Result { - Ok(VariantExcludeAttr::default()) -} + pub(crate) const AS_VARIANT: &str = "AsVariant"; + pub(crate) const AS_VARIANT_REF: &str = "AsVariantRef"; + pub(crate) const AS_VARIANT_MUT: &str = "AsVariantMut"; -fn index_of_variant_field_with_name( - variant: &Variant, - name: &str, - meta: &MetaNameValue, -) -> Result { - match &variant.fields { - Fields::Named(fields) => { - if let Some(index) = fields - .named - .iter() - .position(|field| field.ident.as_ref().unwrap() == name) - { - Ok(index) - } else { - Err(Error::new_spanned( - meta, - format!("no field named {name:?} on variant"), - )) - } - } - Fields::Unnamed(_fields) => Err(Error::new_spanned( - meta, - "name-based field selector (e.g. `field = \"name\"`) not supported on tuple variant", - )), - Fields::Unit => Err(Error::new_spanned(meta, "unit variant has no fields")), - } + pub(crate) const IS_VARIANT: &str = "IsVariant"; + pub(crate) const VARIANT_DISCRIMINANT: &str = "VariantDiscriminant"; + pub(crate) const VARIANT_DOWNCAST: &str = "VariantDowncast"; } -fn index_of_variant_field_with_index( - variant: &Variant, - index: usize, - meta: &MetaNameValue, -) -> Result { - match &variant.fields { - Fields::Named(_fields) => Err(Error::new_spanned( - meta, - "index-based field selector (e.g. `field = 1`) not supported on struct variant", - )), - Fields::Unnamed(fields) => { - let fields_len = fields.unnamed.len(); - if fields_len > index { - Ok(index) - } else { - Err(Error::new_spanned( - meta, - format!("variant only has {fields_len} fields"), - )) - } - } - Fields::Unit => Err(Error::new_spanned(meta, "unit variant has no fields")), +pub(crate) fn position_of_selected_field( + fields: &syn::Fields, + config: Option<&VariantFieldConfig>, +) -> Result, syn::Error> { + let field_count = fields.len(); + + if field_count > 1 && config.is_none() { + return Err(syn::Error::new_spanned( + fields, + "multiple ambiguous fields in variant", + )); } -} -fn variant_include_field_attr( - variant: &Variant, - meta: &MetaNameValue, -) -> Result { - match &meta.value { - Expr::Lit(expr_lit) => match &expr_lit.lit { - Lit::Str(lit) => { - let index = index_of_variant_field_with_name(variant, &lit.value(), meta)?; - Ok(VariantIncludeFieldAttr { index }) - } - Lit::Int(lit) => { - let index = index_of_variant_field_with_index(variant, lit.base10_parse()?, meta)?; - Ok(VariantIncludeFieldAttr { index }) + if let Some(field_config) = config { + match field_config { + VariantFieldConfig::Name(name) => { + let position = fields + .iter() + .position(|field| { + field + .ident + .as_ref() + .map(|ident| ident == name) + .unwrap_or(false) + }) + .expect("field name should have been rejected"); + + return Ok(Some(position)); } - _ => Err(Error::new_spanned( - meta, - "expected number or string literal!", - )), - }, - _ => Err(Error::new_spanned(meta, "unsupported literal!")), - } -} - -fn variant_include_attr( - variant: &Variant, - meta: &ParseNestedMeta, -) -> Result { - let mut attr = VariantIncludeAttr::default(); - let content; - syn::parenthesized!(content in meta.input); - - let metas = content.parse_terminated(Meta::parse, Token![,])?; - - for meta in metas { - match &meta { - Meta::Path(_) => return Err(Error::new_spanned(meta, "not supported!")), - Meta::List(_) => return Err(Error::new_spanned(meta, "not supported!")), - Meta::NameValue(name_value) => { - if name_value.path.is_ident(FIELD_ATTR) { - attr.field = variant_include_field_attr(variant, name_value)?; - } else { - return Err(Error::new_spanned(meta, "not supported!")); - } + VariantFieldConfig::Index(index) => { + assert!( + field_count > *index, + "field index should have been rejected" + ); + return Ok(Some(*index)); } } } - Ok(attr) -} - -fn variant_attrs(variant: &Variant) -> Result { - let mut attrs = VariantAttrs::default(); - - for attr in &variant.attrs { - if !attr.path().is_ident(NAMESPACE_ATTR) { - continue; - } - - attr.parse_nested_meta(|meta| { - if meta.path.is_ident(EXCLUDE_ATTR) { - attrs.exclude = Some(variant_exclude_attr(variant, &meta)?); - Ok(()) - } else if meta.path.is_ident(INCLUDE_ATTR) { - attrs.include = Some(variant_include_attr(variant, &meta)?); - Ok(()) - } else { - Err(meta.error("unsupported attribute")) - } - })?; - } - - Ok(attrs) -} - -fn variant_fields(variant: &Variant) -> Result, Error> { - let fields = match &variant.fields { - Fields::Named(fields) => Vec::from_iter(fields.named.iter()), - Fields::Unnamed(fields) => Vec::from_iter(fields.unnamed.iter()), - Fields::Unit => vec![], - } - .into_iter() - .map(|field| FieldInfo { - ident: field.ident.clone(), - ty: &field.ty, - }) - .collect(); - - Ok(fields) -} - -pub(crate) fn variant_infos<'a, I>(variants: I) -> Result>, Error> -where - I: IntoIterator, -{ - let mut info = vec![]; - - for variant in variants { - info.push(VariantInfo { - ident: variant_ident(variant)?, - attrs: variant_attrs(variant)?, - fields: variant_fields(variant)?, - }); + match field_count { + 0 => Ok(None), + 1 => Ok(Some(0)), + _ => Err(syn::Error::new_spanned(fields, "more than one field")), } - - Ok(info) } #[track_caller] From bd124cf893bf8d46e2600a9e45e4e1656ac2f32c Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 18 Nov 2024 17:11:10 +0100 Subject: [PATCH 3/6] Add unit tests for config parsing & validation logic of `enumcapsulate-macros` --- macros/src/config/tests.rs | 495 +++++++++++++++++++++++++++++++++++++ 1 file changed, 495 insertions(+) create mode 100644 macros/src/config/tests.rs diff --git a/macros/src/config/tests.rs b/macros/src/config/tests.rs new file mode 100644 index 0000000..1297721 --- /dev/null +++ b/macros/src/config/tests.rs @@ -0,0 +1,495 @@ +use syn::parse_quote; + +mod macro_idents { + use crate::config::*; + + use super::*; + + #[test] + fn parses_no_list() -> Result<(), syn::Error> { + let attr: syn::Attribute = syn::parse_quote! { + #[namespace(no_list)] + }; + + let mut actual: Vec = vec![]; + + attr.parse_nested_meta(|meta| { + assert!(meta.path.is_ident("no_list")); + + actual.extend(parse_idents_from_meta_list(&meta)?); + + Ok(()) + })?; + + assert!(actual.is_empty()); + + Ok(()) + } + + #[test] + fn parses_empty_list() -> Result<(), syn::Error> { + let attr: syn::Attribute = syn::parse_quote! { + #[namespace(empty_list())] + }; + + let mut actual: Vec = vec![]; + + attr.parse_nested_meta(|meta| { + assert!(meta.path.is_ident("empty_list")); + + actual.extend(parse_idents_from_meta_list(&meta)?); + + Ok(()) + })?; + + assert!(actual.is_empty()); + + Ok(()) + } + + #[test] + fn parses_non_empty_list() -> Result<(), syn::Error> { + let attr: syn::Attribute = syn::parse_quote! { + #[namespace(non_empty_list(Foo, Bar, Baz))] + }; + + let mut actual: Vec = vec![]; + + attr.parse_nested_meta(|meta| { + assert!(meta.path.is_ident("non_empty_list")); + + actual.extend(parse_idents_from_meta_list(&meta)?); + + Ok(()) + })?; + + let expected: Vec = vec![ + parse_quote! { Foo }, + parse_quote! { Bar }, + parse_quote! { Baz }, + ]; + + assert_eq!(actual.len(), expected.len()); + assert_eq!(actual, expected); + + Ok(()) + } + + #[test] + fn accepts_valid_list() -> Result<(), syn::Error> { + let idents: Vec = vec![parse_quote! { Foo }, parse_quote! { Bar }]; + + let recognized: Vec<&str> = vec!["Foo", "Bar"]; + ensure_only_recognized_ident_names(&idents, &recognized)?; + + let conflicting: Vec = vec![parse_quote! { Baz }, parse_quote! { Blee }]; + ensure_no_conflicting_idents(&idents, &conflicting)?; + + Ok(()) + } + + #[test] + fn detects_unrecognized_idents() -> Result<(), syn::Error> { + let idents: Vec = vec![parse_quote! { Foo }, parse_quote! { Unrecognized }]; + + let recognized: Vec<&str> = vec!["Foo", "Bar"]; + let error = ensure_only_recognized_ident_names(&idents, &recognized) + .err() + .unwrap(); + + assert_eq!(error.to_string(), "unrecognized macro derive"); + + Ok(()) + } + + #[test] + fn detects_conflicting_idents() -> Result<(), syn::Error> { + let idents: Vec = vec![parse_quote! { Foo }, parse_quote! { Bar }]; + + let conflicting: Vec = vec![parse_quote! { Bar }]; + let error = ensure_no_conflicting_idents(&idents, &conflicting) + .err() + .unwrap(); + + assert_eq!(error.to_string(), "conflicting macro derive"); + + Ok(()) + } +} + +mod enum_config { + use syn::parse_quote; + + use crate::{config_for_enum_with_attrs, EnumConfig}; + + #[test] + fn accepts_empty_attrs() -> Result<(), syn::Error> { + let attrs: Vec = parse_quote! {}; + + let config = config_for_enum_with_attrs(&attrs)?; + + assert_eq!(config.exclude, None); + + Ok(()) + } + + #[test] + fn accepts_empty_exclude_attrs() -> Result<(), syn::Error> { + let attrs: Vec = parse_quote! { + #[enumcapsulate(exclude)] + }; + + let config = config_for_enum_with_attrs(&attrs)?; + + assert_eq!(config.exclude, Some(vec![])); + + Ok(()) + } + + #[test] + fn accepts_non_empty_exclude_attrs() -> Result<(), syn::Error> { + let attrs: Vec = parse_quote! { + #[enumcapsulate(exclude(AsVariant, IntoVariant))] + }; + + let config = config_for_enum_with_attrs(&attrs)?; + + assert_eq!( + config.exclude, + Some(vec![ + parse_quote! { AsVariant }, + parse_quote! { IntoVariant } + ]) + ); + + Ok(()) + } + + #[test] + fn rejects_unrecognized_exclude_attrs() -> Result<(), syn::Error> { + let attrs: Vec = parse_quote! { + #[enumcapsulate(exclude(IntoVariant, Unrecognized))] + }; + + let error = config_for_enum_with_attrs(&attrs).err().unwrap(); + + assert_eq!(error.to_string(), "unrecognized macro derive"); + + Ok(()) + } + + #[test] + fn is_excluded() { + let config = EnumConfig { + exclude: Some(vec![ + parse_quote! { FromVariant }, + parse_quote! { IntoVariant }, + ]), + }; + + assert_eq!(config.is_excluded("FromVariant"), true); + assert_eq!(config.is_excluded("IntoVariant"), true); + assert_eq!(config.is_excluded("AsVariant"), false); + } +} + +mod variant_config { + use syn::parse_quote; + + use crate::{config_for_variant, EnumConfig, VariantConfig, VariantFieldConfig}; + + #[test] + fn accepts_empty_attrs() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + Dummy(bool) + }; + + let config = config_for_variant(&variant)?; + + assert_eq!(config.exclude, None); + assert_eq!(config.include, None); + assert_eq!(config.field, None); + + Ok(()) + } + + #[test] + fn accepts_field_index_attr_for_unnamed_fields() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(field(index = 2))] + Dummy(i8, i16, i32, i64) + }; + + let config = config_for_variant(&variant)?; + + assert_eq!(config.exclude, None); + assert_eq!(config.include, None); + assert_eq!(config.field, Some(VariantFieldConfig::Index(2))); + + Ok(()) + } + + #[test] + fn accepts_field_index_attr_for_named_fields() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(field(index = 2))] + Dummy { foo: i8, bar: i16, baz: i32, blee: i64 } + }; + + let config = config_for_variant(&variant)?; + + assert_eq!(config.exclude, None); + assert_eq!(config.include, None); + assert_eq!(config.field, Some(VariantFieldConfig::Index(2))); + + Ok(()) + } + + #[test] + fn rejects_invalid_field_index_attr_for_unnamed_fields() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(field(index = 42))] + Dummy(i8, i16, i32, i64) + }; + + let error = config_for_variant(&variant).err().unwrap(); + + assert_eq!(error.to_string(), "field index out of bounds"); + + Ok(()) + } + + #[test] + fn rejects_invalid_field_index_attr_for_named_fields() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(field(index = 42))] + Dummy { foo: i8, bar: i16, baz: i32, blee: i64 } + }; + + let error = config_for_variant(&variant).err().unwrap(); + + assert_eq!(error.to_string(), "field index out of bounds"); + + Ok(()) + } + + #[test] + fn rejects_field_name_attr_for_unnamed_fields() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(field(name = "bar"))] + Dummy(i8, i16, i32, i64) + }; + + let error = config_for_variant(&variant).err().unwrap(); + + assert_eq!(error.to_string(), "no named fields in variant"); + + Ok(()) + } + + #[test] + fn accepts_field_name_attr_for_named_fields() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(field(name = "bar"))] + Dummy { foo: i8, bar: i16, baz: i32, blee: i64 } + }; + + let config = config_for_variant(&variant)?; + + assert_eq!(config.exclude, None); + assert_eq!(config.include, None); + assert_eq!( + config.field, + Some(VariantFieldConfig::Name("bar".to_owned())) + ); + + Ok(()) + } + + #[test] + fn rejects_invalid_field_name_attr_for_named_fields() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(field(name = "invalid"))] + Dummy { foo: i8, bar: i16, baz: i32, blee: i64 } + }; + + let error = config_for_variant(&variant).err().unwrap(); + + assert_eq!(error.to_string(), "field not found in variant"); + + Ok(()) + } + + #[test] + fn accepts_empty_exclude_include_attrs() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(exclude)] + #[enumcapsulate(include)] + Dummy(bool) + }; + + let config = config_for_variant(&variant)?; + + assert_eq!(config.exclude, Some(vec![])); + assert_eq!(config.include, Some(vec![])); + assert_eq!(config.field, None); + + Ok(()) + } + + #[test] + fn accepts_non_empty_exclude_include_attrs() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(exclude(From, TryInto))] + #[enumcapsulate(include(FromVariant, IntoVariant))] + Dummy(bool) + }; + + let config = config_for_variant(&variant)?; + + assert_eq!( + config.exclude, + Some(vec![parse_quote! { From }, parse_quote! { TryInto }]) + ); + assert_eq!( + config.include, + Some(vec![ + parse_quote! { FromVariant }, + parse_quote! { IntoVariant } + ]) + ); + + Ok(()) + } + + #[test] + fn rejects_unrecognized_exclude_attrs() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(exclude(IntoVariant, Unrecognized))] + Dummy(bool) + }; + + let error = config_for_variant(&variant).err().unwrap(); + + assert_eq!(error.to_string(), "unrecognized macro derive"); + + Ok(()) + } + + #[test] + fn rejects_unrecognized_include_attrs() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(include(IntoVariant, Unrecognized))] + Dummy(bool) + }; + + let error = config_for_variant(&variant).err().unwrap(); + + assert_eq!(error.to_string(), "unrecognized macro derive"); + + Ok(()) + } + + #[test] + fn rejects_conflicting_exclude_include_attrs() -> Result<(), syn::Error> { + let variant: syn::Variant = parse_quote! { + #[enumcapsulate(exclude(From, TryInto))] + #[enumcapsulate(include(TryInto, IntoVariant))] + Dummy(bool) + }; + + let error = config_for_variant(&variant).err().unwrap(); + + assert_eq!(error.to_string(), "conflicting macro derive"); + + Ok(()) + } + + mod is_excluded { + use super::*; + + #[test] + fn no_enum_excludes() { + let enum_config = EnumConfig { exclude: None }; + + let config = VariantConfig { + exclude: None, + include: None, + field: None, + }; + + assert_eq!(config.is_excluded("FromVariant", &enum_config), false); + assert_eq!(config.is_excluded("IntoVariant", &enum_config), false); + assert_eq!(config.is_excluded("AsVariant", &enum_config), false); + } + + #[test] + fn only_enum_excludes() { + let enum_config = EnumConfig { + exclude: Some(vec![parse_quote! { AsVariant }]), + }; + + let config = VariantConfig { + exclude: None, + include: None, + field: None, + }; + + assert_eq!(config.is_excluded("FromVariant", &enum_config), false); + assert_eq!(config.is_excluded("IntoVariant", &enum_config), false); + assert_eq!(config.is_excluded("AsVariant", &enum_config), true); + } + + #[test] + fn blanket_overridden_enum_excludes() { + let enum_config = EnumConfig { + exclude: Some(vec![parse_quote! { AsVariant }]), + }; + + let config = VariantConfig { + exclude: None, + include: Some(vec![]), + field: None, + }; + + assert_eq!(config.is_excluded("FromVariant", &enum_config), false); + assert_eq!(config.is_excluded("IntoVariant", &enum_config), false); + assert_eq!(config.is_excluded("AsVariant", &enum_config), false); + } + + #[test] + fn selective_overridden_enum_excludes() { + let enum_config = EnumConfig { + exclude: Some(vec![ + parse_quote! { AsVariant }, + parse_quote! { IntoVariant }, + ]), + }; + + let config = VariantConfig { + exclude: None, + include: Some(vec![parse_quote! { AsVariant }]), + field: None, + }; + + assert_eq!(config.is_excluded("FromVariant", &enum_config), false); + assert_eq!(config.is_excluded("IntoVariant", &enum_config), true); + assert_eq!(config.is_excluded("AsVariant", &enum_config), false); + } + + #[test] + fn selective_overridden_variant_excludes() { + let enum_config = EnumConfig { exclude: None }; + + let config = VariantConfig { + exclude: Some(vec![]), + include: Some(vec![parse_quote! { AsVariant }]), + field: None, + }; + + assert_eq!(config.is_excluded("FromVariant", &enum_config), true); + assert_eq!(config.is_excluded("IntoVariant", &enum_config), true); + assert_eq!(config.is_excluded("AsVariant", &enum_config), false); + } + } +} From 27646c069e10cf28c3ea1ae2822ee0e598d97b94 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 18 Nov 2024 12:18:51 +0100 Subject: [PATCH 4/6] Refine derive-tests --- tests/derive-tests.rs | 17 ++++++----- .../pass/enum/mixed_variants.out.rs | 2 +- .../as_variant/pass/enum/mixed_variants.rs | 2 +- .../pass/enum/mixed_variants.out.rs | 8 +++-- .../pass/enum/mixed_variants.rs | 10 +++---- .../pass/enum/mixed_variants.out.rs | 8 +++-- .../pass/enum/mixed_variants.rs | 8 +++-- .../pass/enum/tuple_variants/one_field.out.rs | 30 +++++++++---------- .../pass/enum/tuple_variants/one_field.rs | 14 +++++---- .../from/pass/enum/mixed_variants.out.rs | 10 +------ .../from/pass/enum/mixed_variants.rs | 16 +--------- .../pass/enum/mixed_variants.out.rs | 21 +++++++++++-- .../from_variant/pass/enum/mixed_variants.rs | 10 +++---- .../pass/enum/mixed_variants.out.rs | 8 +++-- .../into_variant/pass/enum/mixed_variants.rs | 10 +++---- .../pass/enum/mixed_variants.out.rs | 8 +++-- .../is_variant/pass/enum/mixed_variants.rs | 8 +++-- .../try_into/pass/enum/mixed_variants.out.rs | 8 +++-- .../try_into/pass/enum/mixed_variants.rs | 8 +++-- .../pass/mixed_variants.out.rs | 8 +++-- .../variant_downcast/pass/mixed_variants.rs | 8 +++-- 21 files changed, 120 insertions(+), 102 deletions(-) diff --git a/tests/derive-tests.rs b/tests/derive-tests.rs index 3b04cd2..3c47c9f 100644 --- a/tests/derive-tests.rs +++ b/tests/derive-tests.rs @@ -99,6 +99,8 @@ mod is_variant { pub fn pass() { tryexpand::expand(["tests/derive-tests/is_variant/pass/**/*.rs"]).and_check(); } + + // There should be no failures for this derive macro; } mod variant_discriminant { @@ -106,6 +108,8 @@ mod variant_discriminant { pub fn pass() { tryexpand::expand(["tests/derive-tests/variant_discriminant/pass/**/*.rs"]).and_check(); } + + // There should be no failures for this derive macro; } mod encapsulate { @@ -114,12 +118,9 @@ mod encapsulate { tryexpand::expand(["tests/derive-tests/encapsulate/pass/**/*.rs"]).and_check(); } - #[test] - pub fn fail() { - // the failures are already covered by the tests of the individual - // derives that this umbrella derive delegates to. - // - // As such we only have to make sure in `pass()` that - // it does actually derive what it says on the tin. - } + // Failures are already covered by the tests of the individual + // derives that this umbrella derive delegates to. + // + // As such we only have to make sure in `pass()` that + // it does actually derive what it says on the tin. } diff --git a/tests/derive-tests/as_variant/pass/enum/mixed_variants.out.rs b/tests/derive-tests/as_variant/pass/enum/mixed_variants.out.rs index 16334b3..af320e1 100644 --- a/tests/derive-tests/as_variant/pass/enum/mixed_variants.out.rs +++ b/tests/derive-tests/as_variant/pass/enum/mixed_variants.out.rs @@ -21,7 +21,7 @@ pub enum Enum { OneTupleField(VariantA), OneStructField { variant: VariantB }, #[enumcapsulate(exclude)] - OneExcludedTupleField(VariantA), + Excluded(VariantA, VariantB), } impl ::enumcapsulate::AsVariant for Enum where diff --git a/tests/derive-tests/as_variant/pass/enum/mixed_variants.rs b/tests/derive-tests/as_variant/pass/enum/mixed_variants.rs index 0ef085f..c76efef 100644 --- a/tests/derive-tests/as_variant/pass/enum/mixed_variants.rs +++ b/tests/derive-tests/as_variant/pass/enum/mixed_variants.rs @@ -14,7 +14,7 @@ pub enum Enum { variant: VariantB, }, #[enumcapsulate(exclude)] - OneExcludedTupleField(VariantA), + Excluded(VariantA, VariantB), } fn main() { diff --git a/tests/derive-tests/as_variant_mut/pass/enum/mixed_variants.out.rs b/tests/derive-tests/as_variant_mut/pass/enum/mixed_variants.out.rs index 2a926ad..2631904 100644 --- a/tests/derive-tests/as_variant_mut/pass/enum/mixed_variants.out.rs +++ b/tests/derive-tests/as_variant_mut/pass/enum/mixed_variants.out.rs @@ -9,13 +9,15 @@ pub enum Enum { ZeroStructFields {}, OneTupleField(VariantA), OneStructField { variant: VariantB }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32 }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD }, } impl ::enumcapsulate::AsVariantMut for Enum { diff --git a/tests/derive-tests/as_variant_mut/pass/enum/mixed_variants.rs b/tests/derive-tests/as_variant_mut/pass/enum/mixed_variants.rs index 2c23990..1e8a955 100644 --- a/tests/derive-tests/as_variant_mut/pass/enum/mixed_variants.rs +++ b/tests/derive-tests/as_variant_mut/pass/enum/mixed_variants.rs @@ -14,16 +14,18 @@ pub enum Enum { OneStructField { variant: VariantB, }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32, }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD, @@ -31,8 +33,6 @@ pub enum Enum { } fn main() { - - let mut subject = Enum::Unit; { diff --git a/tests/derive-tests/as_variant_ref/pass/enum/mixed_variants.out.rs b/tests/derive-tests/as_variant_ref/pass/enum/mixed_variants.out.rs index c62802f..91deeaa 100644 --- a/tests/derive-tests/as_variant_ref/pass/enum/mixed_variants.out.rs +++ b/tests/derive-tests/as_variant_ref/pass/enum/mixed_variants.out.rs @@ -37,13 +37,15 @@ pub enum Enum { ZeroStructFields {}, OneTupleField(VariantA), OneStructField { variant: VariantB }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32 }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD }, } impl ::enumcapsulate::AsVariantRef for Enum { diff --git a/tests/derive-tests/as_variant_ref/pass/enum/mixed_variants.rs b/tests/derive-tests/as_variant_ref/pass/enum/mixed_variants.rs index 5637e66..8859def 100644 --- a/tests/derive-tests/as_variant_ref/pass/enum/mixed_variants.rs +++ b/tests/derive-tests/as_variant_ref/pass/enum/mixed_variants.rs @@ -18,16 +18,18 @@ pub enum Enum { OneStructField { variant: VariantB, }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32, }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD, diff --git a/tests/derive-tests/encapsulate/pass/enum/tuple_variants/one_field.out.rs b/tests/derive-tests/encapsulate/pass/enum/tuple_variants/one_field.out.rs index 8bcbbf8..2da175d 100644 --- a/tests/derive-tests/encapsulate/pass/enum/tuple_variants/one_field.out.rs +++ b/tests/derive-tests/encapsulate/pass/enum/tuple_variants/one_field.out.rs @@ -1,4 +1,6 @@ -use enumcapsulate::{AsVariant, AsVariantMut, AsVariantRef, Encapsulate, IntoVariant}; +use enumcapsulate::{ + AsVariant, AsVariantMut, AsVariantRef, Encapsulate, FromVariant, IntoVariant, +}; pub struct VariantA; #[automatically_derived] impl ::core::clone::Clone for VariantA { @@ -15,11 +17,9 @@ impl ::core::clone::Clone for VariantB { VariantB } } -pub struct VariantC; -pub struct VariantD; pub enum Enum { VariantA(VariantA), - VariantB(VariantB), + VariantB { b: VariantB }, } impl ::core::convert::From for Enum { fn from(inner: VariantA) -> Self { @@ -28,7 +28,7 @@ impl ::core::convert::From for Enum { } impl ::core::convert::From for Enum { fn from(inner: VariantB) -> Self { - Self::VariantB(inner) + Self::VariantB { b: inner } } } impl ::core::convert::TryFrom for VariantA { @@ -44,7 +44,7 @@ impl ::core::convert::TryFrom for VariantB { type Error = Enum; fn try_from(outer: Enum) -> Result { match outer { - Enum::VariantB(inner, ..) => Ok(inner), + Enum::VariantB { b: inner, .. } => Ok(inner), err => Err(err), } } @@ -56,7 +56,7 @@ impl ::enumcapsulate::FromVariant for Enum { } impl ::enumcapsulate::FromVariant for Enum { fn from_variant(inner: VariantB) -> Self { - Self::VariantB(inner) + Self::VariantB { b: inner } } } impl ::enumcapsulate::AsVariant for Enum @@ -76,7 +76,7 @@ where { fn as_variant(&self) -> Option { match self { - Enum::VariantB(inner, ..) => Some(inner.clone()), + Enum::VariantB { b: inner, .. } => Some(inner.clone()), _ => None, } } @@ -92,7 +92,7 @@ impl ::enumcapsulate::AsVariantRef for Enum { impl ::enumcapsulate::AsVariantRef for Enum { fn as_variant_ref(&self) -> Option<&VariantB> { match self { - Enum::VariantB(inner, ..) => Some(inner), + Enum::VariantB { b: inner, .. } => Some(inner), _ => None, } } @@ -108,7 +108,7 @@ impl ::enumcapsulate::AsVariantMut for Enum { impl ::enumcapsulate::AsVariantMut for Enum { fn as_variant_mut(&mut self) -> Option<&mut VariantB> { match self { - Enum::VariantB(inner, ..) => Some(inner), + Enum::VariantB { b: inner, .. } => Some(inner), _ => None, } } @@ -124,7 +124,7 @@ impl ::enumcapsulate::IntoVariant for Enum { impl ::enumcapsulate::IntoVariant for Enum { fn into_variant(self) -> Result { match self { - Enum::VariantB(inner, ..) => Ok(inner), + Enum::VariantB { b: inner, .. } => Ok(inner), err => Err(err), } } @@ -143,7 +143,7 @@ impl ::enumcapsulate::IsVariant for Enum { let type_id = TypeId::of::(); match self { Enum::VariantA(inner, ..) => type_id_of_val(inner) == type_id, - Enum::VariantB(inner, ..) => type_id_of_val(inner) == type_id, + Enum::VariantB { b: inner, .. } => type_id_of_val(inner) == type_id, _ => false, } } @@ -206,15 +206,15 @@ impl ::enumcapsulate::VariantDiscriminant for Enum { fn variant_discriminant(&self) -> Self::Discriminant { match self { Enum::VariantA(..) => EnumDiscriminant::VariantA, - Enum::VariantB(..) => EnumDiscriminant::VariantB, + Enum::VariantB { .. } => EnumDiscriminant::VariantB, _ => ::core::panicking::panic("internal error: entered unreachable code"), } } } fn check() where - T: AsVariant + AsVariantRef + AsVariantMut + IntoVariant + From - + TryInto, + T: FromVariant + IntoVariant + From + TryInto + AsVariant + + AsVariantRef + AsVariantMut, {} fn main() { check::(); diff --git a/tests/derive-tests/encapsulate/pass/enum/tuple_variants/one_field.rs b/tests/derive-tests/encapsulate/pass/enum/tuple_variants/one_field.rs index 68801a4..c36830e 100644 --- a/tests/derive-tests/encapsulate/pass/enum/tuple_variants/one_field.rs +++ b/tests/derive-tests/encapsulate/pass/enum/tuple_variants/one_field.rs @@ -1,21 +1,25 @@ -use enumcapsulate::{AsVariant, AsVariantMut, AsVariantRef, Encapsulate, IntoVariant}; +use enumcapsulate::{AsVariant, AsVariantMut, AsVariantRef, Encapsulate, FromVariant, IntoVariant}; #[derive(Clone)] pub struct VariantA; #[derive(Clone)] pub struct VariantB; -pub struct VariantC; -pub struct VariantD; #[derive(Encapsulate)] pub enum Enum { VariantA(VariantA), - VariantB(VariantB), + VariantB { b: VariantB }, } fn check() where - T: AsVariant + AsVariantRef + AsVariantMut + IntoVariant + From + TryInto, + T: FromVariant + + IntoVariant + + From + + TryInto + + AsVariant + + AsVariantRef + + AsVariantMut, { } diff --git a/tests/derive-tests/from/pass/enum/mixed_variants.out.rs b/tests/derive-tests/from/pass/enum/mixed_variants.out.rs index 648adb0..a03b63a 100644 --- a/tests/derive-tests/from/pass/enum/mixed_variants.out.rs +++ b/tests/derive-tests/from/pass/enum/mixed_variants.out.rs @@ -1,22 +1,14 @@ use enumcapsulate::From; pub struct VariantA; pub struct VariantB; -pub struct VariantC; -pub struct VariantD; pub enum Enum { Unit, ZeroTupleFields(), ZeroStructFields {}, OneTupleField(VariantA), OneStructField { variant: VariantB }, - TwoTupleFields(i32, u32), - TwoStructFields { a: i32, b: u32 }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] - IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] - IncludedStruct { value: u8, variant: VariantD }, + Excluded(VariantA, VariantB), } impl ::core::convert::From for Enum { fn from(inner: VariantA) -> Self { diff --git a/tests/derive-tests/from/pass/enum/mixed_variants.rs b/tests/derive-tests/from/pass/enum/mixed_variants.rs index 8db1894..018d8c5 100644 --- a/tests/derive-tests/from/pass/enum/mixed_variants.rs +++ b/tests/derive-tests/from/pass/enum/mixed_variants.rs @@ -2,8 +2,6 @@ use enumcapsulate::From; pub struct VariantA; pub struct VariantB; -pub struct VariantC; -pub struct VariantD; #[derive(From)] pub enum Enum { @@ -14,20 +12,8 @@ pub enum Enum { OneStructField { variant: VariantB, }, - TwoTupleFields(i32, u32), - TwoStructFields { - a: i32, - b: u32, - }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] - IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] - IncludedStruct { - value: u8, - variant: VariantD, - }, + Excluded(VariantA, VariantB), } fn main() { diff --git a/tests/derive-tests/from_variant/pass/enum/mixed_variants.out.rs b/tests/derive-tests/from_variant/pass/enum/mixed_variants.out.rs index 2f71131..03e6e7f 100644 --- a/tests/derive-tests/from_variant/pass/enum/mixed_variants.out.rs +++ b/tests/derive-tests/from_variant/pass/enum/mixed_variants.out.rs @@ -9,13 +9,15 @@ pub enum Enum { ZeroStructFields {}, OneTupleField(VariantA), OneStructField { variant: VariantB }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32 }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD }, } impl ::enumcapsulate::FromVariant for Enum { @@ -30,6 +32,19 @@ impl ::enumcapsulate::FromVariant for Enum { } } } +impl ::enumcapsulate::FromVariant for Enum { + fn from_variant(inner: VariantC) -> Self { + Self::IncludedTuple(Default::default(), inner) + } +} +impl ::enumcapsulate::FromVariant for Enum { + fn from_variant(inner: VariantD) -> Self { + Self::IncludedStruct { + value: Default::default(), + variant: inner, + } + } +} fn main() { let _ = Enum::from_variant(VariantA); let _ = Enum::from_variant(VariantB); diff --git a/tests/derive-tests/from_variant/pass/enum/mixed_variants.rs b/tests/derive-tests/from_variant/pass/enum/mixed_variants.rs index 12aaede..c167a2d 100644 --- a/tests/derive-tests/from_variant/pass/enum/mixed_variants.rs +++ b/tests/derive-tests/from_variant/pass/enum/mixed_variants.rs @@ -14,16 +14,18 @@ pub enum Enum { OneStructField { variant: VariantB, }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32, }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD, @@ -31,8 +33,6 @@ pub enum Enum { } fn main() { - - let _ = Enum::from_variant(VariantA); let _ = Enum::from_variant(VariantB); } diff --git a/tests/derive-tests/into_variant/pass/enum/mixed_variants.out.rs b/tests/derive-tests/into_variant/pass/enum/mixed_variants.out.rs index 157d287..c15e47e 100644 --- a/tests/derive-tests/into_variant/pass/enum/mixed_variants.out.rs +++ b/tests/derive-tests/into_variant/pass/enum/mixed_variants.out.rs @@ -9,13 +9,15 @@ pub enum Enum { ZeroStructFields {}, OneTupleField(VariantA), OneStructField { variant: VariantB }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32 }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD }, } impl ::enumcapsulate::IntoVariant for Enum { diff --git a/tests/derive-tests/into_variant/pass/enum/mixed_variants.rs b/tests/derive-tests/into_variant/pass/enum/mixed_variants.rs index b17eb08..0a8363f 100644 --- a/tests/derive-tests/into_variant/pass/enum/mixed_variants.rs +++ b/tests/derive-tests/into_variant/pass/enum/mixed_variants.rs @@ -14,16 +14,18 @@ pub enum Enum { OneStructField { variant: VariantB, }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32, }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD, @@ -31,8 +33,6 @@ pub enum Enum { } fn main() { - - { let subject = Enum::Unit; let _: Result = subject.into_variant(); diff --git a/tests/derive-tests/is_variant/pass/enum/mixed_variants.out.rs b/tests/derive-tests/is_variant/pass/enum/mixed_variants.out.rs index 425e4e5..34b36b1 100644 --- a/tests/derive-tests/is_variant/pass/enum/mixed_variants.out.rs +++ b/tests/derive-tests/is_variant/pass/enum/mixed_variants.out.rs @@ -9,13 +9,15 @@ pub enum Enum { ZeroStructFields {}, OneTupleField(VariantA), OneStructField { variant: VariantB }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32 }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD }, } impl ::enumcapsulate::IsVariant for Enum { diff --git a/tests/derive-tests/is_variant/pass/enum/mixed_variants.rs b/tests/derive-tests/is_variant/pass/enum/mixed_variants.rs index 5bc2ea2..1f623e0 100644 --- a/tests/derive-tests/is_variant/pass/enum/mixed_variants.rs +++ b/tests/derive-tests/is_variant/pass/enum/mixed_variants.rs @@ -14,16 +14,18 @@ pub enum Enum { OneStructField { variant: VariantB, }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32, }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD, diff --git a/tests/derive-tests/try_into/pass/enum/mixed_variants.out.rs b/tests/derive-tests/try_into/pass/enum/mixed_variants.out.rs index ca7bbde..9a96121 100644 --- a/tests/derive-tests/try_into/pass/enum/mixed_variants.out.rs +++ b/tests/derive-tests/try_into/pass/enum/mixed_variants.out.rs @@ -9,13 +9,15 @@ pub enum Enum { ZeroStructFields {}, OneTupleField(VariantA), OneStructField { variant: VariantB }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32 }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD }, } impl ::core::convert::TryFrom for VariantA { diff --git a/tests/derive-tests/try_into/pass/enum/mixed_variants.rs b/tests/derive-tests/try_into/pass/enum/mixed_variants.rs index a1b3971..95ccf2d 100644 --- a/tests/derive-tests/try_into/pass/enum/mixed_variants.rs +++ b/tests/derive-tests/try_into/pass/enum/mixed_variants.rs @@ -14,16 +14,18 @@ pub enum Enum { OneStructField { variant: VariantB, }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32, }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD, diff --git a/tests/derive-tests/variant_downcast/pass/mixed_variants.out.rs b/tests/derive-tests/variant_downcast/pass/mixed_variants.out.rs index 0c35286..cdfdfed 100644 --- a/tests/derive-tests/variant_downcast/pass/mixed_variants.out.rs +++ b/tests/derive-tests/variant_downcast/pass/mixed_variants.out.rs @@ -37,13 +37,15 @@ pub enum Enum { ZeroStructFields {}, OneTupleField(VariantA), OneStructField { variant: VariantB }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32 }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD }, } impl ::enumcapsulate::AsVariant for Enum diff --git a/tests/derive-tests/variant_downcast/pass/mixed_variants.rs b/tests/derive-tests/variant_downcast/pass/mixed_variants.rs index dc399f7..a7bd109 100644 --- a/tests/derive-tests/variant_downcast/pass/mixed_variants.rs +++ b/tests/derive-tests/variant_downcast/pass/mixed_variants.rs @@ -18,16 +18,18 @@ pub enum Enum { OneStructField { variant: VariantB, }, + #[enumcapsulate(exclude)] TwoTupleFields(i32, u32), + #[enumcapsulate(exclude)] TwoStructFields { a: i32, b: u32, }, #[enumcapsulate(exclude)] - Excluded(bool), - #[enumcapsulate(include(field = 1))] + Excluded(VariantA, VariantB), + #[enumcapsulate(field(index = 1))] IncludedTuple(i8, VariantC), - #[enumcapsulate(include(field = "variant"))] + #[enumcapsulate(field(name = "variant"))] IncludedStruct { value: u8, variant: VariantD, From f8264115169a78545d18fb725484abfce22464b5 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 18 Nov 2024 12:07:10 +0100 Subject: [PATCH 5/6] Update "README.md" file of `enumcapsulate-macros` crate --- macros/README.md | 346 +++++++++++++++++++++++++++++++---------------- 1 file changed, 230 insertions(+), 116 deletions(-) diff --git a/macros/README.md b/macros/README.md index eaa631a..be62b42 100644 --- a/macros/README.md +++ b/macros/README.md @@ -13,171 +13,285 @@ Derive macros for [enumcapsulate](https://crates.io/crates/enumcapsulate) crate. The `enumcapsulate-macros` proc-macro crate exports the following derive macros: -| Derive macro | Functionality | -| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `AsVariant` | Derives impls for `enumcapsulate::AsVariant` for each variant type `T` where `T: Clone` | -| `AsVariantMut` | Derives impls for `enumcapsulate::AsVariantMut` for each variant type `T` | -| `AsVariantRef` | Derives impls for `enumcapsulate::AsVariantRef` for each variant type `T` | -| `Encapsulate` | Umbrella derive macro for `AsVariant`, `AsVariantMut`, `AsVariantRef`, `From`, `FromVariant`, `IntoVariant`, `IsVariant`, `TryInto`, `VariantDiscriminant`, and `VariantDowncast` | -| `From` | Derives impls for `core::convert::From` for each variant type `T` | -| `FromVariant` | Derives impls for `enumcapsulate::FromVariant` for each variant type `T` | -| `IntoVariant` | Derives impls for `enumcapsulate::FromVariant` for each variant type `T` | -| `IsVariant` | Derives impl for `enumcapsulate::IsVariant` | -| `TryInto` | Derives impls for `core::convert::TryInto` for each variant type `T` | -| `VariantDiscriminant` | Derives impl for `enumcapsulate::VariantDiscriminant` | - -# Macro helper attributes +| Derive macro | Functionality | +| --------------------- | -------------------------------------------------------------------------------------------- | +| `FromVariant` | Derives impls for `Self: enumcapsulate::FromVariant` for each non-unit variant type `T`. | +| `IntoVariant` | Derives impls for `Self: enumcapsulate::FromVariant` for each non-unit variant type `T`. | +| `From` | Derives impls for `Self: core::convert::From` for each non-unit variant type `T`. | +| `TryInto` | Derives impls for `T: core::convert::TryFrom` for each non-unit variant type `T`. | +| `AsVariant` | Derives impls for `Self: enumcapsulate::AsVariant` for each non-unit variant type `T`. | +| `AsVariantMut` | Derives impls for `Self: enumcapsulate::AsVariantMut` for each non-unit variant type `T`. | +| `AsVariantRef` | Derives impls for `Self: enumcapsulate::AsVariantRef` for each non-unit variant type `T`. | +| `IsVariant` | Derives impl for `enumcapsulate::IsVariant`. | +| `VariantDiscriminant` | Derives impl for `Self: enumcapsulate::VariantDiscriminant`. | -Most of the derive macros support helper attributes: +… as well as an umbrella derive macro `Encapsulate`. -## `#[enumcapsulate(exclude)]` +> [!NOTE] +> The implementations generated by the `From` and `FromVariant`, as well as the `TryInto` and `IntoVariant` derive macro pairs are semantically equivalent. -The variant-based derive macros in this crate will perform derives for **every single-field variant** they find in the enum. -This can lead to undesired false positives where a variant like `ToBeExcluded` unintentionally -gets detected as variant of type `bool`. +### `#[derive(Encapsulate)]` -Given an enum like this … +The umbrella derive macro `Encapsulate` allows for conveniently deriving `AsVariant`, `AsVariantMut`, `AsVariantRef`, `From`, `FromVariant`, `IntoVariant`, `IsVariant`, `TryInto`, `VariantDiscriminant`, and `VariantDowncast` all at once. -```rust -struct VariantA { - // ... -} +The following two snippets are semantically equivalent: -struct VariantB { - // ... -} +```rust +#[derive(Encapsulate)] +enum Enum { /* ... */ } +``` -#[derive(FromVariant)] -enum Enum { - VariantA(VariantA), - VariantB(VariantB), - ToBeExcluded { flagToBeExcluded: bool }, -} +```rust +#[derive( + FromVariant, + IntoVariant, + From, + TryInto, + AsVariant, + AsVariantMut, + AsVariantRef, + IsVariant, + VariantDiscriminant, +)] +enum Enum { /* ... */ } ``` -… the following implementations get derived from the code above: +> [!TIP] +> If the list of trait derives produced by `#[derive(Encapsulate)]` is too broad of a stroke +> your your particular use case then your can selectively opt them out by use of an +> `#[enumcapsulate(exclude(…))]` attribute on the enum itself. +> +> You can even opt trait derives back in at the variant-level, by use of `#[enumcapsulate(include)]` +> for previously opted-out derives, or individually by use of `#[enumcapsulate(include(…))]`: +> +> ```rust +> #[derive(Encapsulate)] +> #[enumcapsulate(exclude(From, TryInto))] +> enum Enum { +> // Excluded from `From` and `TryInto` derives +> // due to existing enum-level attribute: +> VariantA(VariantA), +> +> // Selectively re-included for all derives: +> #[enumcapsulate(include)] +> VariantB(VariantB), +> +> // Selectively re-included for `From` derive: +> #[enumcapsulate(include(From))] +> VariantC(VariantC), +> } +> ``` + +### `#[derive(AsVariant)]` + +The `AsVariant` derive macro requires the variant's field type to implement `Clone`. + +## Macro helper attributes -```rust -impl FromVariant for Enum { - // ... -} +Most of the derive macros support helper attributes: -impl FromVariant for Enum { - // ... -} +### Enum attributes -// Notice how the derive picked up the `is_cool: bool` field -// as an inner variant and generated an impl for it: +#### `#[enumcapsulate(exclude(…))]` -impl FromVariant for Enum { - // ... -} +Exclude this variant from trait derivation. + +- `#[enumcapsulate(exclude(…))]` + + Exclude variant from specific `enumcapsulate` derive macros. + +If you wish to opt out of a select few of `Encapsulate`'s trait derives, +then you can do so by use of an `#[enumcapsulate(exclude(…))]` attribute: + +```rust +#[derive(Encapsulate)] +#[enumcapsulate(exclude(From, TryInto))] +enum Enum { /* ... */ } ``` -Adding `#[enumcapsulate(exclude)]` to the undesired variant … +> [!TIP] +> If you wish to opt all but a select few variants out of a trait's derive, then +> you can do so by use of an `#[enumcapsulate(exclude(…))]` attribute on the enum, +> together with a `#[enumcapsulate(include(…))]` attribute on the variant: +> +> ```rust +> #[derive(Encapsulate)] +> #[enumcapsulate(exclude(From, TryInto))] +> enum Enum { +> // Excluded from `From` and `TryInto` derives +> // due to existing enum-level attribute: +> VariantA(VariantA), +> +> // Selectively re-included for all derives: +> #[enumcapsulate(include)] +> VariantB(VariantB), +> +> // Selectively re-included for +> // just the `From` derive: +> #[enumcapsulate(include(From))] +> VariantC(VariantC), +> +> // ... +> } +> ``` + +### Variant attributes + +> [!NOTE] +> Variant-level attributes have a higher precedence than their equivalent enum-level attributes +> and thus act as a selective override if both are present. + +#### `#[enumcapsulate(exclude(…))]` + +Exclude this variant from trait derivation. + +- `#[enumcapsulate(exclude)]` + + Exclude variant from *all* `enumcapsulate` derive macros. + +- `#[enumcapsulate(exclude(…))]` + + Exclude variant from specific `enumcapsulate` derive macros. ```rust -#[derive(FromVariant)] +#[derive(Encapsulate)] enum Enum { - // ... + // Included by default. + VariantA(VariantA), + + // Excluded from all derives: #[enumcapsulate(exclude)] - ToBeExcluded { flag: bool }, + VariantB(VariantB), + + // Excluded from just the `From` and `TryInto` derives: + #[enumcapsulate(exclude(From, TryInto))] + VariantC(VariantC), } ``` -… makes the undesired `impl FromVariant for Enum` get omitted. +> [!TIP] +> Combine the use of `#[enumcapsulate(exclude)]` with `#[enumcapsulate(include(…)]` +> in order to exclude a variant from all but a select few derive macros. +> +> ```rust +> // Exclude variant from all derives, +> // then selectively re-include it for +> // just the `From` and `TryInto` derives: +> #[enumcapsulate(exclude)] +> #[enumcapsulate(include(From, TryInto))] +> ``` -## `#[enumcapsulate(include(field = …_)]` +This attribute is recognized by the following variant-based derive macros: -The variant-based derive macros in this crate will skip derives for **any multi-field variant** they find in the enum. +- `AsVariant` +- `AsVariantMut` +- `AsVariantRef` +- `FromVariant` +- `IntoVariant` +- `From` +- `TryInto` -This can lead to undesired false negatives where a variant like `ToBeIncluded` unintentionally -gets detected as variant of type `bool`. +… as well as the umbrella derive macro: -For tuple variants the field can be specified by its index: `#[enumcapsulate(include(field = INDEX))]` -For struct variants the field can be specified by its name: `#[enumcapsulate(include(field = "NAME"))]` +- `Encapsulate` -Given an enum like this … +#### `#[enumcapsulate(include(…)]` -```rust -struct VariantA { - // ... -} +Include this variant for specific trait derivation (overriding existing uses of `#[enumcapsulate(exclude)]`). -struct VariantB { - // ... -} +- `#[enumcapsulate(include)]` -struct VariantC { - // ... -} + Include variant from *all* `enumcapsulate` derive macros. + +- `#[enumcapsulate(include(…))]` + + Include variant from specific `enumcapsulate` derive macros. -#[derive(FromVariant)] +```rust +#[derive(Encapsulate)] +#[enumcapsulate(exclude(From, TryInto))] enum Enum { + // Included by default. VariantA(VariantA), - ToBeIncludedB(bool, VariantB), - ToBeIncludedC { flag: bool, variant: VariantC }, + + // Selectively included for just + // the `From` and `TryInto` derives: + #[enumcapsulate(exclude)] + #[enumcapsulate(include(From, TryInto))] + VariantB(VariantB), } ``` -… the following implementations get derived from the code above: +> [!NOTE] +> The `#[enumcapsulate(include(…)]` variant has a higher precedence than `#[enumcapsulate(exclude)]`, +> and thus acts as a selective override if both are present on a variant: +> +> ```rust +> // Exclude variant from all derives, +> // then selectively re-include it for +> // just the `From` and `TryInto` derives: +> #[enumcapsulate(exclude)] +> #[enumcapsulate(include(From, TryInto))] +> ``` -```rust -impl FromVariant for Enum { - // ... -} +This attribute is recognized by the following variant-based derive macros: -// Notice how the variants `ToBeIncludedB { … }` and `ToBeIncludedC { … }` -// produced no impls, due to having more than one field. -``` +- `AsVariant` +- `AsVariantMut` +- `AsVariantRef` +- `FromVariant` +- `IntoVariant` +- `From` +- `TryInto` -Adding `#[enumcapsulate(include(field = …))]` to the desired variant, -with the desired variant field specified … +… as well as the umbrella derive macro: + +- `Encapsulate` + +#### `#[enumcapsulate(field(… = …)]` + +Select a specific variant field as target for trait derivation. + +- `#[enumcapsulate(field(index = ))]` + + Select field at index `` to be used as target. + +- `#[enumcapsulate(field(name = ""))]` + + Select field with name `` to be used as target. ```rust -#[derive(FromVariant)] +#[derive(Encapsulate)] enum Enum { - // ... - #[enumcapsulate(include(field = 1))] - ToBeIncludedB(bool, VariantB), - #[enumcapsulate(include(field = "variant"))] - ToBeIncludedC { flag: bool, variant: VariantC }, + // Select field `1`, i.e. `VariantA`: + #[enumcapsulate(field(index = 1))] + VariantA(i32, VariantA), + + // Select field `b`, i.e. `VariantB`: + #[enumcapsulate(field(name = "b"))] + VariantB { b: VariantB, c: u32 }, } ``` -… makes the variants have their `impl FromVariant<…> for Enum` get derived as desired: +> [!NOTE] +> When deriving traits for variants with multiple fields a field +> has to be explicitly specified using `#[enumcapsulate(field(… = …))]`. +> +> Alternatively the variant can be excluded via `#[enumcapsulate(exclude)]`. -```rust -// ... +This attribute is recognized by the following variant-based derive macros: -impl FromVariant for Enum { - // ... -} +- `AsVariant` +- `AsVariantMut` +- `AsVariantRef` +- `FromVariant` +- `IntoVariant` +- `From` +- `TryInto` -impl FromVariant for Enum { - // ... -} -``` +… as well as the umbrella derive macro: -Note however that `#[derive(From)]` and `#[derive(FromVariant)]` (at the current time) -still won't generate impls for variants with more than one field. - -## Helper attribute support - -Check the matrix below for which derive macros support which helper attributes: - -| | `#[enumcapsulate(exclude)]` | `#[enumcapsulate(include(field = …))]` | -| --------------------- | --------------------------- | -------------------------------------- | -| `AsVariant` | ✔ supported | ✔ supported | -| `AsVariantMut` | ✔ supported | ✔ supported | -| `AsVariantRef` | ✔ supported | ✔ supported | -| `Encapsulate` | ✔ supported | ✔ supported | -| `From` | ✔ supported | ✘ not supported | -| `FromVariant` | ✔ supported | ✘ not supported | -| `IntoVariant` | ✔ supported | ✔ supported | -| `IsVariant` | ✔ supported | ✔ supported | -| `TryInto` | ✔ supported | ✔ supported | -| `VariantDiscriminant` | ✘ not supported | ✘ not supported | +- `Encapsulate` ## Documentation From 3ff3e5de2ef4396f221ccac8cfe9f90bb2558c15 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 18 Nov 2024 18:02:49 +0100 Subject: [PATCH 6/6] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e033cc6..23e1e07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ Please make sure to add your changes to the appropriate categories: - Added dedicated `trait AsVariant` for `AsVariantRef`'s removed `fn as_variant(&self) -> Option`. - Added dedicated `AsVariant` derive macro for `trait AsVariant`. +- Added enum-level `#[enumcapsulate(exclude)]`/`#[enumcapsulate(exclude(…))]` helper attribute. +- Added optional selection list to variant-level `#[enumcapsulate(exclude(…))]` helper attribute. +- Added optional selection list to variant-level `#[enumcapsulate(include(…))]` helper attribute. ### Changed @@ -29,6 +32,9 @@ Please make sure to add your changes to the appropriate categories: - Before: `use enumcapsulate::{derive::FromVariant, FromVariant};` - After: `use enumcapsulate::FromVariant;` - Made `Encapsulate` derive macro derive also derive `AsVariant`. +- Promoted `field` value of `include(field = …)` to its own top-level variant attribute with name/index variants: + - `#[enumcapsulate(field(index = …))]` + - `#[enumcapsulate(field(name = "…"))]` ### Deprecated