Skip to content

Commit 8135c8d

Browse files
committed
Add support for nested variant discriminants
(via `#[enumcapsulate(discriminant(nested = …))]` variant attribute)
1 parent 338ea23 commit 8135c8d

File tree

13 files changed

+817
-135
lines changed

13 files changed

+817
-135
lines changed

macros/src/config/for_variant/config.rs

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ pub(crate) enum FieldSelector {
1414
Index(usize),
1515
}
1616

17+
impl Default for FieldSelector {
18+
fn default() -> Self {
19+
Self::Index(0)
20+
}
21+
}
22+
1723
#[derive(Clone, Default)]
1824
pub(crate) struct VariantConfig {
1925
discriminant: Option<DiscriminantConfig>,
@@ -60,42 +66,41 @@ impl VariantConfig {
6066
Ok(this)
6167
}
6268

63-
pub(crate) fn position_of_selected_field(
69+
pub(crate) fn selected_field<'a>(
6470
&self,
65-
fields: &syn::Fields,
66-
) -> Result<usize, syn::Error> {
67-
if let Some(selector) = &self.field {
68-
match selector {
69-
FieldSelector::Name(name) => {
70-
let position = fields
71-
.iter()
72-
.position(|field| {
73-
field
74-
.ident
75-
.as_ref()
76-
.map(|ident| ident == name)
77-
.unwrap_or(false)
78-
})
79-
.expect("field name should have been rejected");
80-
81-
return Ok(position);
82-
}
83-
FieldSelector::Index(index) => {
84-
if *index >= fields.len() {
85-
return Err(syn::Error::new_spanned(fields, "field index out of bounds"));
86-
}
87-
return Ok(*index);
88-
}
71+
fields: &'a syn::Fields,
72+
) -> Result<Option<(&'a syn::Field, usize)>, syn::Error> {
73+
let default_field_selector = FieldSelector::default();
74+
let field_selector = self.field.as_ref().unwrap_or(&default_field_selector);
75+
76+
if fields.is_empty() {
77+
return Ok(None);
78+
}
79+
80+
match field_selector {
81+
FieldSelector::Name(name) => {
82+
let (index, field) = fields
83+
.iter()
84+
.enumerate()
85+
.find(|(_, field)| {
86+
field
87+
.ident
88+
.as_ref()
89+
.map(|ident| ident == name)
90+
.unwrap_or(false)
91+
})
92+
.expect("field name should have been rejected");
93+
94+
Ok(Some((field, index)))
95+
}
96+
FieldSelector::Index(index) => {
97+
let field = fields
98+
.iter()
99+
.nth(*index)
100+
.expect("field index should have been rejected");
101+
102+
Ok(Some((field, *index)))
89103
}
90-
};
91-
92-
match fields.len() {
93-
0 => Err(syn::Error::new_spanned(fields, "no fields")),
94-
1 => Ok(0),
95-
_ => Err(syn::Error::new_spanned(
96-
fields,
97-
"multiple fields, please disambiguate via helper attribute",
98-
)),
99104
}
100105
}
101106

@@ -106,4 +111,9 @@ impl VariantConfig {
106111
pub(crate) fn exclude(&self) -> Option<&ExcludeConfig> {
107112
self.exclude.as_ref()
108113
}
114+
115+
#[allow(dead_code)]
116+
pub(crate) fn field(&self) -> Option<&FieldSelector> {
117+
self.field.as_ref()
118+
}
109119
}

macros/src/config/for_variant/discriminant.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,45 @@
11
use syn::meta::ParseNestedMeta;
22

3-
use crate::attr::{NAME, VALUE};
3+
use crate::attr::{NAME, NESTED, VALUE};
44

55
#[derive(Clone, Default)]
66
pub(crate) struct DiscriminantConfig {
77
expr: Option<syn::Expr>,
88
ident: Option<syn::Ident>,
9+
nested: Option<syn::Path>,
910
}
1011

1112
impl DiscriminantConfig {
1213
pub(crate) fn parse(
1314
&mut self,
1415
meta: &ParseNestedMeta,
15-
_variant: &syn::Variant,
16+
variant: &syn::Variant,
1617
) -> Result<(), syn::Error> {
1718
meta.parse_nested_meta(|meta| {
1819
if meta.path.is_ident(VALUE) {
1920
if self.expr.is_some() {
20-
return Err(meta.error("value already specified"));
21+
return Err(meta.error("`value = …` already specified"));
22+
} else if self.nested.is_some() {
23+
return Err(meta.error("conflicting with use of `nesting = …`"));
2124
}
2225

2326
self.expr = Some(meta.value()?.parse()?);
2427
} else if meta.path.is_ident(NAME) {
2528
if self.ident.is_some() {
26-
return Err(meta.error("name already specified"));
29+
return Err(meta.error("`name = …` already specified"));
2730
}
2831

2932
self.ident = Some(meta.value()?.parse()?);
33+
} else if meta.path.is_ident(NESTED) {
34+
if matches!(variant.fields, syn::Fields::Unit) {
35+
return Err(meta.error("no field found on variant"));
36+
} else if self.nested.is_some() {
37+
return Err(meta.error("`nested = …` already specified"));
38+
} else if self.expr.is_some() {
39+
return Err(meta.error("conflicting with use of `value = …`"));
40+
}
41+
42+
self.nested = Some(meta.value()?.parse()?);
3043
} else {
3144
return Err(meta.error("unsupported discriminant attribute"));
3245
}
@@ -42,4 +55,8 @@ impl DiscriminantConfig {
4255
pub(crate) fn ident(&self) -> Option<&syn::Ident> {
4356
self.ident.as_ref()
4457
}
58+
59+
pub(crate) fn nested(&self) -> Option<&syn::Path> {
60+
self.nested.as_ref()
61+
}
4562
}

0 commit comments

Comments
 (0)