Skip to content

Commit

Permalink
Make only the Encapsulate derive macros recognize the enum-level `#…
Browse files Browse the repository at this point in the history
…[enumcapsulate(exclude(…))]` helper macro
  • Loading branch information
regexident committed Nov 30, 2024
1 parent a69a28b commit 082405b
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 168 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Please make sure to add your changes to the appropriate categories:
### Changed

- Bumped MSRV from "1.74.0" to "1.78.0".
- Made the enum-level `#[enumcapsulate(exclude(…))]` helper macros only have an effect on derives, when orchestrated through the `Encapsulate` derive macro.

### Deprecated

Expand Down
3 changes: 3 additions & 0 deletions macros/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ Exclude this variant from trait derivation.
Exclude variant from specific `enumcapsulate` derive macros.
> [!IMPORTANT]
> This attribute is only recognized by the `Encapsulate` umbrella derive macro.
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:
Expand Down
49 changes: 38 additions & 11 deletions macros/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ pub(crate) type VariantExcludeConfig = MacroSelectionConfig;
pub(crate) type VariantIncludeConfig = MacroSelectionConfig;

#[derive(Clone, Default)]
pub(crate) struct EnumConfig {
pub(crate) struct EncapsulateEnumConfig {
// #[enumcapsulate(exclude(…))]
pub exclude: Option<EnumExcludeConfig>,
}

impl EnumConfig {
impl EncapsulateEnumConfig {
pub fn is_included(&self, name: &str) -> bool {
!self.is_excluded(name)
}
Expand All @@ -76,6 +76,9 @@ impl EnumConfig {
}
}

#[derive(Clone, Default)]
pub(crate) struct EnumConfig {}

#[derive(Clone, Default)]
pub(crate) struct VariantConfig {
// #[enumcapsulate(exclude(…))]
Expand All @@ -89,17 +92,16 @@ pub(crate) struct VariantConfig {
}

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;
}

pub fn is_excluded(&self, name: &str) -> bool {
if self.is_included_explicitly(name) {
return false;
}

config.is_excluded(name)
if self.is_excluded_explicitly(name) {
return true;
}

false
}

pub fn is_excluded_explicitly(&self, name: &str) -> bool {
Expand Down Expand Up @@ -135,14 +137,21 @@ impl VariantConfig {
}
}

pub(crate) fn config_for_enum(enum_item: &syn::ItemEnum) -> Result<EnumConfig, syn::Error> {
let mut config = EnumConfig::default();
pub(crate) fn encapsulate_config_for_enum(
enum_item: &syn::ItemEnum,
) -> Result<EncapsulateEnumConfig, syn::Error> {
let mut config = EncapsulateEnumConfig::default();

parse_enumcapsulate_attrs(&enum_item.attrs, |meta| {
if meta.path.is_ident(attr::EXCLUDE) {
// #[enumcapsulate(exclude(…))]

let mut exclude = config.exclude.take().unwrap_or_default();

if exclude.is_empty() {
return Err(meta.error("expected list"));
}

exclude.extend_idents(macro_selection_config_for_enum(&meta)?.idents);

config.exclude = Some(exclude);
Expand All @@ -156,6 +165,24 @@ pub(crate) fn config_for_enum(enum_item: &syn::ItemEnum) -> Result<EnumConfig, s
Ok(config)
}

pub(crate) fn config_for_enum(enum_item: &syn::ItemEnum) -> Result<EnumConfig, syn::Error> {
let config = EnumConfig::default();

parse_enumcapsulate_attrs(&enum_item.attrs, |meta| {
if meta.path.is_ident(attr::EXCLUDE) {
// #[enumcapsulate(exclude(…))]

// Ignored.
} else {
return Err(meta.error("unrecognized attribute"));
}

Ok(())
})?;

Ok(config)
}

pub(crate) fn config_for_variant(variant: &syn::Variant) -> Result<VariantConfig, syn::Error> {
let mut config = VariantConfig::default();

Expand Down
164 changes: 43 additions & 121 deletions macros/src/config/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,71 +128,69 @@ mod enum_config {

let config = config_for_enum(&item)?;

assert_eq!(config.exclude, None);

Ok(())
}

#[test]
fn accepts_empty_exclude_attrs() -> Result<(), syn::Error> {
fn rejects_unrecognized_exclude_attrs() -> Result<(), syn::Error> {
let item: syn::ItemEnum = parse_quote! {
#[enumcapsulate(exclude)]
#[enumcapsulate(exclude(IntoVariant, Unrecognized))]
enum Dummy {}
};

let config = config_for_enum(&item)?;

let actual: Vec<syn::Ident> = config.exclude.unwrap().idents;
let expected: Vec<syn::Ident> = vec![];
let error = config_for_enum(&item).err().unwrap();

assert_eq!(actual, expected);
assert_eq!(error.to_string(), "unrecognized macro derive");

Ok(())
}

#[test]
fn accepts_non_empty_exclude_attrs() -> Result<(), syn::Error> {
let item: syn::ItemEnum = parse_quote! {
#[enumcapsulate(exclude(AsVariant, IntoVariant))]
enum Dummy {}
};
mod encapsulate {
use super::*;

let config = config_for_enum(&item)?;
#[test]
#[should_panic]
fn rejects_empty_exclude_attrs() -> Result<(), syn::Error> {
let item: syn::ItemEnum = parse_quote! {
#[enumcapsulate(exclude)]
enum Dummy {}
};

let actual = config.exclude.unwrap().idents;
let expected: Vec<syn::Ident> =
vec![parse_quote! { AsVariant }, parse_quote! { IntoVariant }];
let config = encapsulate_config_for_enum(&item)?;

assert_eq!(actual, expected);
Ok(())
}

Ok(())
}
#[test]
fn accepts_non_empty_exclude_attrs() -> Result<(), syn::Error> {
let item: syn::ItemEnum = parse_quote! {
#[enumcapsulate(exclude(AsVariant, IntoVariant))]
enum Dummy {}
};

#[test]
fn rejects_unrecognized_exclude_attrs() -> Result<(), syn::Error> {
let item: syn::ItemEnum = parse_quote! {
#[enumcapsulate(exclude(IntoVariant, Unrecognized))]
enum Dummy {}
};
let config = config_for_enum(&item)?;

let error = config_for_enum(&item).err().unwrap();
let actual = config.exclude.unwrap().idents;
let expected: Vec<syn::Ident> =
vec![parse_quote! { AsVariant }, parse_quote! { IntoVariant }];

assert_eq!(error.to_string(), "unrecognized macro derive");
assert_eq!(actual, expected);

Ok(())
}
Ok(())
}

#[test]
fn is_excluded() {
let config = EnumConfig {
exclude: Some(MacroSelectionConfig {
idents: vec![parse_quote! { FromVariant }, parse_quote! { IntoVariant }],
}),
};
#[test]
fn is_excluded() {
let config = EncapsulateEnumConfig {
exclude: Some(MacroSelectionConfig {
idents: 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);
assert_eq!(config.is_excluded("FromVariant"), true);
assert_eq!(config.is_excluded("IntoVariant"), true);
assert_eq!(config.is_excluded("AsVariant"), false);
}
}
}

Expand Down Expand Up @@ -416,84 +414,8 @@ mod variant_config {
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(MacroSelectionConfig {
idents: 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(MacroSelectionConfig {
idents: vec![parse_quote! { AsVariant }],
}),
};

let config = VariantConfig {
exclude: None,
include: Some(MacroSelectionConfig { idents: 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(MacroSelectionConfig {
idents: vec![parse_quote! { AsVariant }, parse_quote! { IntoVariant }],
}),
};

let config = VariantConfig {
exclude: None,
include: Some(MacroSelectionConfig {
idents: 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(MacroSelectionConfig { idents: vec![] }),
include: Some(MacroSelectionConfig {
Expand All @@ -502,9 +424,9 @@ mod variant_config {
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);
assert_eq!(config.is_excluded("FromVariant"), true);
assert_eq!(config.is_excluded("IntoVariant"), true);
assert_eq!(config.is_excluded("AsVariant"), false);
}
}
}
Loading

0 comments on commit 082405b

Please sign in to comment.