Skip to content

Commit 26b95df

Browse files
committed
Add support for #[enumcapsulate(discriminant(…))] enum/variant-level helper attributes
1 parent efdece7 commit 26b95df

File tree

13 files changed

+416
-27
lines changed

13 files changed

+416
-27
lines changed

macros/src/config/for_enum.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
mod discriminant;
12
mod encapsulate;
23
mod standard;
4+
mod variant_discriminant;
35

46
pub(crate) use self::encapsulate::EncapsulateDeriveEnumConfig;
7+
pub(crate) use self::variant_discriminant::VariantDiscriminantDeriveEnumConfig;
58

6-
use self::standard::EnumConfig;
9+
use self::{discriminant::DiscriminantConfig, standard::EnumConfig};
710

811
pub(crate) type FromDeriveEnumConfig = EnumConfig;
912
pub(crate) type TryIntoDeriveEnumConfig = EnumConfig;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use syn::meta::ParseNestedMeta;
2+
3+
use crate::attr::{NAME, REPR};
4+
5+
#[derive(Clone, Default)]
6+
pub(crate) struct DiscriminantConfig {
7+
repr: Option<syn::Type>,
8+
ident: Option<syn::Ident>,
9+
}
10+
11+
impl DiscriminantConfig {
12+
pub(crate) fn parse(
13+
&mut self,
14+
meta: &ParseNestedMeta,
15+
_item_enum: &syn::ItemEnum,
16+
) -> Result<(), syn::Error> {
17+
meta.parse_nested_meta(|meta| {
18+
if meta.path.is_ident(REPR) {
19+
if self.repr.is_some() {
20+
return Err(meta.error("repr already specified"));
21+
}
22+
23+
self.repr = Some(meta.value()?.parse()?);
24+
} else if meta.path.is_ident(NAME) {
25+
if self.ident.is_some() {
26+
return Err(meta.error("name already specified"));
27+
}
28+
29+
self.ident = Some(meta.value()?.parse()?);
30+
} else {
31+
return Err(meta.error("unsupported discriminant attribute"));
32+
}
33+
34+
Ok(())
35+
})
36+
}
37+
38+
pub(crate) fn repr(&self) -> Option<&syn::Type> {
39+
self.repr.as_ref()
40+
}
41+
42+
pub(crate) fn ident(&self) -> Option<&syn::Ident> {
43+
self.ident.as_ref()
44+
}
45+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use crate::{attr::DISCRIMINANT, parse_enumcapsulate_attrs};
2+
3+
use super::DiscriminantConfig;
4+
5+
#[derive(Clone, Default)]
6+
pub(crate) struct VariantDiscriminantDeriveEnumConfig {
7+
discriminant: Option<DiscriminantConfig>,
8+
}
9+
10+
impl VariantDiscriminantDeriveEnumConfig {
11+
pub(crate) fn from_enum(item_enum: &syn::ItemEnum) -> Result<Self, syn::Error> {
12+
let mut this = Self::default();
13+
14+
parse_enumcapsulate_attrs(&item_enum.attrs, |meta| {
15+
if meta.path.is_ident(DISCRIMINANT) {
16+
let mut discriminant = this.discriminant.take().unwrap_or_default();
17+
18+
discriminant.parse(&meta, item_enum)?;
19+
20+
this.discriminant = Some(discriminant);
21+
}
22+
23+
Ok(())
24+
})?;
25+
26+
Ok(this)
27+
}
28+
29+
pub fn repr(&self) -> Option<&syn::Type> {
30+
self.discriminant
31+
.as_ref()
32+
.and_then(|discriminant| discriminant.repr())
33+
}
34+
35+
pub fn ident(&self) -> Option<&syn::Ident> {
36+
self.discriminant
37+
.as_ref()
38+
.and_then(|discriminant| discriminant.ident())
39+
}
40+
}

macros/src/config/for_variant.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
mod discriminant;
12
mod standard;
3+
mod variant_discriminant;
24

3-
use self::standard::VariantConfig;
5+
pub(crate) use self::variant_discriminant::VariantDiscriminantDeriveVariantConfig;
6+
7+
use self::{discriminant::DiscriminantConfig, standard::VariantConfig};
48

59
pub(crate) type FromDeriveVariantConfig = VariantConfig;
610
pub(crate) type TryIntoDeriveVariantConfig = VariantConfig;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use syn::meta::ParseNestedMeta;
2+
3+
use crate::attr::{NAME, VALUE};
4+
5+
#[derive(Clone, Default)]
6+
pub(crate) struct DiscriminantConfig {
7+
expr: Option<syn::Expr>,
8+
ident: Option<syn::Ident>,
9+
}
10+
11+
impl DiscriminantConfig {
12+
pub(crate) fn parse(
13+
&mut self,
14+
meta: &ParseNestedMeta,
15+
_variant: &syn::Variant,
16+
) -> Result<(), syn::Error> {
17+
meta.parse_nested_meta(|meta| {
18+
if meta.path.is_ident(VALUE) {
19+
if self.expr.is_some() {
20+
return Err(meta.error("value already specified"));
21+
}
22+
23+
self.expr = Some(meta.value()?.parse()?);
24+
} else if meta.path.is_ident(NAME) {
25+
if self.ident.is_some() {
26+
return Err(meta.error("name already specified"));
27+
}
28+
29+
self.ident = Some(meta.value()?.parse()?);
30+
} else {
31+
return Err(meta.error("unsupported discriminant attribute"));
32+
}
33+
34+
Ok(())
35+
})
36+
}
37+
38+
pub(crate) fn expr(&self) -> Option<&syn::Expr> {
39+
self.expr.as_ref()
40+
}
41+
42+
pub(crate) fn ident(&self) -> Option<&syn::Ident> {
43+
self.ident.as_ref()
44+
}
45+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use crate::{attr::DISCRIMINANT, parse_enumcapsulate_attrs};
2+
3+
use super::DiscriminantConfig;
4+
5+
#[derive(Clone, Default)]
6+
pub(crate) struct VariantDiscriminantDeriveVariantConfig {
7+
discriminant: Option<DiscriminantConfig>,
8+
}
9+
10+
impl VariantDiscriminantDeriveVariantConfig {
11+
pub(crate) fn from_variant(variant: &syn::Variant) -> Result<Self, syn::Error> {
12+
let mut this = Self::default();
13+
14+
parse_enumcapsulate_attrs(&variant.attrs, |meta| {
15+
if meta.path.is_ident(DISCRIMINANT) {
16+
let mut discriminant = this.discriminant.take().unwrap_or_default();
17+
18+
discriminant.parse(&meta, variant)?;
19+
20+
this.discriminant = Some(discriminant);
21+
}
22+
23+
Ok(())
24+
})?;
25+
26+
Ok(this)
27+
}
28+
29+
pub fn expr(&self) -> Option<&syn::Expr> {
30+
self.discriminant
31+
.as_ref()
32+
.and_then(|discriminant| discriminant.expr())
33+
}
34+
35+
pub fn ident(&self) -> Option<&syn::Ident> {
36+
self.discriminant
37+
.as_ref()
38+
.and_then(|discriminant| discriminant.ident())
39+
}
40+
}

macros/src/enum_deriver.rs

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -552,60 +552,83 @@ impl EnumDeriver {
552552
pub fn derive_variant_discriminant(&self) -> Result<TokenStream2, syn::Error> {
553553
let enum_ident = &self.item.ident;
554554

555-
let outer = enum_ident;
556-
let outer_ty: Type = parse_quote_spanned! { outer.span() => #outer };
555+
let enum_config = VariantDiscriminantDeriveEnumConfig::from_enum(&self.item)?;
556+
557+
let mut discriminant_enum_ident = quote::format_ident!("{enum_ident}Discriminant");
558+
let mut repr_attr: Option<TokenStream2> = None;
559+
560+
if let Some(ident) = enum_config.ident() {
561+
discriminant_enum_ident = ident.clone();
562+
}
563+
564+
if let Some(ty) = enum_config.repr() {
565+
repr_attr = Some(quote! {
566+
#[repr(#ty)]
567+
});
568+
}
557569

558570
let (impl_generics, type_generics, where_clause) = self.item.generics.split_for_impl();
559571

560572
let variants = self.variants();
561573

562-
let discriminant_ident = quote::format_ident!("{outer}Discriminant");
563-
564574
let mut discriminant_variants: Vec<TokenStream2> = vec![];
575+
let mut match_arms: Vec<TokenStream2> = vec![];
565576

566577
for variant in &variants {
567-
let variant_ident = &variant.ident;
578+
let variant_config = VariantDiscriminantDeriveVariantConfig::from_variant(variant)?;
568579

569-
discriminant_variants.push(quote! {
570-
#variant_ident,
571-
});
572-
}
580+
let variant_ident: &syn::Ident = &variant.ident;
573581

574-
let discriminant_enum = quote! {
575-
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
576-
pub enum #discriminant_ident {
577-
#(#discriminant_variants)*
582+
let mut discriminant_variant_ident: syn::Ident = variant_ident.clone();
583+
let mut discriminant_variant_expr: Option<&syn::Expr> =
584+
variant.discriminant.as_ref().map(|(_, expr)| expr);
585+
586+
if let Some(ident) = variant_config.ident() {
587+
discriminant_variant_ident = ident.clone();
578588
}
579-
};
580589

581-
let mut match_arms: Vec<TokenStream2> = vec![];
590+
if let Some(expr) = variant_config.expr() {
591+
discriminant_variant_expr = Some(expr);
592+
}
582593

583-
for variant in variants {
584-
let variant_ident = &variant.ident;
585-
let inner = variant_ident;
594+
let variant_discriminant = discriminant_variant_expr.map(|expr| {
595+
quote! { = #expr }
596+
});
597+
598+
discriminant_variants.push(quote! {
599+
#discriminant_variant_ident #variant_discriminant,
600+
});
586601

587602
let pattern = match &variant.fields {
588603
Fields::Named(_) => quote! {
589-
#outer_ty::#inner { .. }
604+
#enum_ident::#variant_ident { .. }
590605
},
591606
Fields::Unnamed(_) => quote! {
592-
#outer_ty::#inner(..)
607+
#enum_ident::#variant_ident(..)
593608
},
594609
Fields::Unit => quote! {
595-
#outer_ty::#inner
610+
#enum_ident::#variant_ident
596611
},
597612
};
598613

599614
match_arms.push(quote! {
600-
#pattern => #discriminant_ident::#inner,
615+
#pattern => #discriminant_enum_ident::#discriminant_variant_ident,
601616
});
602617
}
603618

619+
let discriminant_enum = quote! {
620+
#repr_attr
621+
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
622+
pub enum #discriminant_enum_ident {
623+
#(#discriminant_variants)*
624+
}
625+
};
626+
604627
Ok(quote! {
605628
#discriminant_enum
606629

607-
impl #impl_generics ::enumcapsulate::VariantDiscriminant for #outer_ty #type_generics #where_clause {
608-
type Discriminant = #discriminant_ident;
630+
impl #impl_generics ::enumcapsulate::VariantDiscriminant for #enum_ident #type_generics #where_clause {
631+
type Discriminant = #discriminant_enum_ident;
609632

610633
fn variant_discriminant(&self) -> Self::Discriminant {
611634
match self {

macros/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ pub fn derive_variant_downcast(input: TokenStream) -> TokenStream {
348348
/// }
349349
/// ```
350350
///
351-
#[proc_macro_derive(VariantDiscriminant)]
351+
#[proc_macro_derive(VariantDiscriminant, attributes(enumcapsulate))]
352352
pub fn derive_variant_discriminant(input: TokenStream) -> TokenStream {
353353
let item = parse_macro_input!(input as syn::ItemEnum);
354354

macros/src/utils.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ use proc_macro::TokenStream;
22
use proc_macro2::TokenStream as TokenStream2;
33

44
pub(crate) mod attr {
5+
#![allow(dead_code)]
6+
7+
pub(crate) const DISCRIMINANT: &str = "discriminant";
58
pub(crate) const EXCLUDE: &str = "exclude";
69
pub(crate) const FIELD: &str = "field";
10+
pub(crate) const NAME: &str = "name";
711
pub(crate) const NAMESPACE: &str = "enumcapsulate";
12+
pub(crate) const REPR: &str = "repr";
13+
pub(crate) const VALUE: &str = "value";
814
}
915

1016
pub(crate) mod macro_name {

0 commit comments

Comments
 (0)