Skip to content

Commit efdece7

Browse files
committed
Refactor config types and parsing
1 parent f0621f1 commit efdece7

File tree

25 files changed

+678
-741
lines changed

25 files changed

+678
-741
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Please make sure to add your changes to the appropriate categories:
2828
- Made the enum-level `#[enumcapsulate(exclude(…))]` helper macros only have an effect on derives, when orchestrated through the `Encapsulate` derive macro.
2929
- Made `Encapsulate` derive macro no longer derive `VariantDiscriminant`.
3030
- Removed potentially confusing `#[enumcapsulate(include(…))]` helper attributes.
31+
- Merged `#[enumcapsulate(field(index = …))]` and `#[enumcapsulate(field(name = "…"))]` into a single polymorph `#[enumcapsulate(field = …)]`
3132

3233
### Deprecated
3334

macros/src/config.rs

Lines changed: 5 additions & 302 deletions
Original file line numberDiff line numberDiff line change
@@ -1,239 +1,10 @@
1-
use syn::{parse::Parse, punctuated::Punctuated};
1+
use crate::attr;
22

3-
use crate::{attr, macro_name};
3+
mod exclude;
4+
mod for_enum;
5+
mod for_variant;
46

5-
static RECOGNIZED_ENUM_LEVEL_MACROS: &[&str] = &[
6-
macro_name::FROM,
7-
macro_name::TRY_INTO,
8-
macro_name::FROM_VARIANT,
9-
macro_name::INTO_VARIANT,
10-
macro_name::AS_VARIANT,
11-
macro_name::AS_VARIANT_REF,
12-
macro_name::AS_VARIANT_MUT,
13-
macro_name::VARIANT_DISCRIMINANT,
14-
macro_name::VARIANT_DOWNCAST,
15-
];
16-
17-
static RECOGNIZED_VARIANT_LEVEL_MACROS: &[&str] = &[
18-
macro_name::FROM,
19-
macro_name::TRY_INTO,
20-
macro_name::FROM_VARIANT,
21-
macro_name::INTO_VARIANT,
22-
macro_name::AS_VARIANT,
23-
macro_name::AS_VARIANT_REF,
24-
macro_name::AS_VARIANT_MUT,
25-
];
26-
27-
#[derive(Clone, Eq, PartialEq, Debug)]
28-
pub(crate) enum VariantFieldConfig {
29-
Name(String),
30-
Index(usize),
31-
}
32-
33-
#[derive(Clone, Default, PartialEq, Eq, Debug)]
34-
pub(crate) struct MacroSelectionConfig {
35-
pub idents: Vec<syn::Ident>,
36-
}
37-
38-
impl MacroSelectionConfig {
39-
pub fn is_empty(&self) -> bool {
40-
self.idents.is_empty()
41-
}
42-
43-
pub fn contains(&self, name: &str) -> bool {
44-
self.idents.iter().any(|ident| ident == name)
45-
}
46-
47-
pub fn extend_idents(&mut self, iter: impl IntoIterator<Item = syn::Ident>) {
48-
self.idents.extend(iter);
49-
}
50-
}
51-
52-
pub(crate) type EnumExcludeConfig = MacroSelectionConfig;
53-
pub(crate) type VariantExcludeConfig = MacroSelectionConfig;
54-
55-
#[derive(Clone, Default)]
56-
pub(crate) struct EncapsulateEnumConfig {
57-
// #[enumcapsulate(exclude(…))]
58-
pub exclude: Option<EnumExcludeConfig>,
59-
}
60-
61-
impl EncapsulateEnumConfig {
62-
pub fn is_included(&self, name: &str) -> bool {
63-
!self.is_excluded(name)
64-
}
65-
66-
pub fn is_excluded(&self, name: &str) -> bool {
67-
self.exclude
68-
.as_ref()
69-
.map(|excluded| excluded.contains(name))
70-
.unwrap_or(false)
71-
}
72-
}
73-
74-
#[derive(Clone, Default)]
75-
pub(crate) struct EnumConfig {}
76-
77-
#[derive(Clone, Default)]
78-
pub(crate) struct VariantConfig {
79-
// #[enumcapsulate(exclude(…))]
80-
pub exclude: Option<VariantExcludeConfig>,
81-
82-
// #[enumcapsulate(field(…))]
83-
pub field: Option<VariantFieldConfig>,
84-
}
85-
86-
impl VariantConfig {
87-
#[allow(dead_code)]
88-
pub fn is_included(&self, name: &str) -> bool {
89-
!self.is_excluded(name)
90-
}
91-
92-
pub fn is_excluded(&self, name: &str) -> bool {
93-
let Some(excluded) = &self.exclude else {
94-
return false;
95-
};
96-
97-
if excluded.is_empty() {
98-
true
99-
} else {
100-
excluded.contains(name)
101-
}
102-
}
103-
}
104-
105-
pub(crate) fn encapsulate_config_for_enum(
106-
enum_item: &syn::ItemEnum,
107-
) -> Result<EncapsulateEnumConfig, syn::Error> {
108-
let mut config = EncapsulateEnumConfig::default();
109-
110-
parse_enumcapsulate_attrs(&enum_item.attrs, |meta| {
111-
if meta.path.is_ident(attr::EXCLUDE) {
112-
// #[enumcapsulate(exclude(…))]
113-
114-
let mut exclude = config.exclude.take().unwrap_or_default();
115-
116-
let idents = macro_selection_config_for_enum(&meta)?.idents;
117-
118-
if idents.is_empty() {
119-
return Err(meta.error("expected list"));
120-
}
121-
122-
exclude.extend_idents(idents);
123-
124-
config.exclude = Some(exclude);
125-
} else {
126-
return Err(meta.error("unrecognized attribute"));
127-
}
128-
129-
Ok(())
130-
})?;
131-
132-
Ok(config)
133-
}
134-
135-
pub(crate) fn config_for_enum(enum_item: &syn::ItemEnum) -> Result<EnumConfig, syn::Error> {
136-
let config = EnumConfig::default();
137-
138-
parse_enumcapsulate_attrs(&enum_item.attrs, |meta| {
139-
if meta.path.is_ident(attr::EXCLUDE) {
140-
// #[enumcapsulate(exclude(…))]
141-
142-
meta.parse_nested_meta(|_meta| {
143-
// Here we're not interested in any of the existing
144-
// sub-attributes, but we need to parse the list anyway.
145-
146-
Ok(())
147-
})?;
148-
} else {
149-
return Err(meta.error("unrecognized attribute"));
150-
}
151-
152-
Ok(())
153-
})?;
154-
155-
Ok(config)
156-
}
157-
158-
pub(crate) fn config_for_variant(variant: &syn::Variant) -> Result<VariantConfig, syn::Error> {
159-
let mut config = VariantConfig::default();
160-
161-
let fields = match &variant.fields {
162-
syn::Fields::Named(fields) => fields.named.iter().collect(),
163-
syn::Fields::Unnamed(fields) => fields.unnamed.iter().collect(),
164-
syn::Fields::Unit => vec![],
165-
};
166-
167-
parse_enumcapsulate_attrs(&variant.attrs, |meta| {
168-
if meta.path.is_ident(attr::EXCLUDE) {
169-
// #[enumcapsulate(exclude(…))]
170-
171-
let mut exclude = config.exclude.take().unwrap_or_default();
172-
173-
exclude.extend_idents(macro_selection_config_for_variant(&meta)?.idents);
174-
175-
config.exclude = Some(exclude);
176-
} else if meta.path.is_ident(attr::FIELD) {
177-
// #[enumcapsulate(field(…))]
178-
meta.parse_nested_meta(|meta| {
179-
if meta.path.is_ident(attr::NAME) {
180-
// #[enumcapsulate(field(name = "…"))]
181-
182-
if !matches!(&variant.fields, syn::Fields::Named(_)) {
183-
return Err(meta.error("no named fields in variant"));
184-
}
185-
186-
let lit: syn::LitStr = meta.value()?.parse()?;
187-
let name = lit.value();
188-
189-
let field_idents: Vec<_> = fields
190-
.iter()
191-
.filter_map(|&field| field.ident.as_ref())
192-
.collect();
193-
194-
if field_idents.is_empty() {
195-
return Err(meta.error("no named fields in variant"));
196-
}
197-
198-
let field_exists = field_idents.into_iter().any(|ident| ident == &name);
199-
200-
if !field_exists {
201-
return Err(meta.error("field not found in variant"));
202-
}
203-
204-
config.field = Some(VariantFieldConfig::Name(name));
205-
206-
Ok(())
207-
} else if meta.path.is_ident(attr::INDEX) {
208-
// #[enumcapsulate(field(index = …))]
209-
210-
if fields.is_empty() {
211-
return Err(meta.error("no fields in variant"));
212-
}
213-
214-
let lit: syn::LitInt = meta.value()?.parse()?;
215-
let index = lit.base10_parse()?;
216-
217-
if fields.len() <= index {
218-
return Err(meta.error("field index out of bounds"));
219-
}
220-
221-
config.field = Some(VariantFieldConfig::Index(index));
222-
223-
Ok(())
224-
} else {
225-
return Err(meta.error("unrecognized attribute"));
226-
}
227-
})?;
228-
} else {
229-
return Err(meta.error("unrecognized attribute"));
230-
}
231-
232-
Ok(())
233-
})?;
234-
235-
Ok(config)
236-
}
7+
pub(crate) use self::{exclude::*, for_enum::*, for_variant::*};
2378

2389
pub(crate) fn parse_enumcapsulate_attrs(
23910
attrs: &[syn::Attribute],
@@ -246,79 +17,11 @@ pub(crate) fn parse_enumcapsulate_attrs(
24617
continue;
24718
}
24819

249-
// #[enumcapsulate(…)]
25020
attr.parse_nested_meta(&mut logic)?;
25121
}
25222

25323
Ok(())
25424
}
25525

256-
pub(crate) fn macro_selection_config_for_enum(
257-
meta: &syn::meta::ParseNestedMeta<'_>,
258-
) -> Result<MacroSelectionConfig, syn::Error> {
259-
let idents = parse_idents_from_meta_list(meta)?;
260-
261-
let recognized = RECOGNIZED_ENUM_LEVEL_MACROS;
262-
ensure_only_recognized_ident_names(&idents, recognized)?;
263-
264-
Ok(MacroSelectionConfig { idents })
265-
}
266-
267-
pub(crate) fn macro_selection_config_for_variant(
268-
meta: &syn::meta::ParseNestedMeta<'_>,
269-
) -> Result<MacroSelectionConfig, syn::Error> {
270-
let idents = parse_idents_from_meta_list(meta)?;
271-
272-
let recognized = RECOGNIZED_VARIANT_LEVEL_MACROS;
273-
ensure_only_recognized_ident_names(&idents, recognized)?;
274-
275-
Ok(MacroSelectionConfig { idents })
276-
}
277-
278-
pub(crate) fn ensure_only_recognized_ident_names(
279-
idents: &[syn::Ident],
280-
recognized: &[&str],
281-
) -> Result<(), syn::Error> {
282-
let mut error: Option<syn::Error> = None;
283-
284-
let unrecognized = idents
285-
.iter()
286-
.filter(|&ident| !recognized.iter().any(|recognized| ident == recognized));
287-
288-
for ident in unrecognized {
289-
let ident_err = syn::Error::new_spanned(ident, "unrecognized macro derive");
290-
if let Some(error) = error.as_mut() {
291-
error.combine(ident_err);
292-
} else {
293-
error = Some(ident_err)
294-
}
295-
}
296-
297-
if let Some(err) = error {
298-
return Err(err);
299-
}
300-
301-
Ok(())
302-
}
303-
304-
pub(crate) fn parse_idents_from_meta_list(
305-
meta: &syn::meta::ParseNestedMeta<'_>,
306-
) -> Result<Vec<syn::Ident>, syn::Error> {
307-
let mut idents = vec![];
308-
309-
let lookahead = meta.input.lookahead1();
310-
if lookahead.peek(syn::token::Paren) {
311-
let content;
312-
syn::parenthesized!(content in meta.input);
313-
314-
let punctuated: Punctuated<syn::Ident, syn::Token![,]> =
315-
content.parse_terminated(syn::Ident::parse, syn::Token![,])?;
316-
317-
idents.extend(punctuated);
318-
}
319-
320-
Ok(idents)
321-
}
322-
32326
#[cfg(test)]
32427
mod tests;

0 commit comments

Comments
 (0)