Skip to content

Commit 9cd50fa

Browse files
committed
Refactor enumcapsulate-macros crate and extend helper attrs
1 parent 4dcdb1d commit 9cd50fa

File tree

4 files changed

+721
-471
lines changed

4 files changed

+721
-471
lines changed

macros/src/config.rs

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
use syn::{parse::Parse, punctuated::Punctuated};
2+
3+
use crate::{attr, macro_name};
4+
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::IS_VARIANT,
14+
macro_name::VARIANT_DISCRIMINANT,
15+
macro_name::VARIANT_DOWNCAST,
16+
];
17+
18+
static RECOGNIZED_VARIANT_LEVEL_MACROS: &[&str] = &[
19+
macro_name::FROM,
20+
macro_name::TRY_INTO,
21+
macro_name::FROM_VARIANT,
22+
macro_name::INTO_VARIANT,
23+
macro_name::AS_VARIANT,
24+
macro_name::AS_VARIANT_REF,
25+
macro_name::AS_VARIANT_MUT,
26+
];
27+
28+
#[derive(Clone, Eq, PartialEq, Debug)]
29+
pub(crate) enum VariantFieldConfig {
30+
Name(String),
31+
Index(usize),
32+
}
33+
34+
#[derive(Clone, Default, Debug)]
35+
pub(crate) struct EnumConfig {
36+
// #[enumcapsulate(exclude(…))]
37+
pub exclude: Option<Vec<syn::Ident>>,
38+
}
39+
40+
impl EnumConfig {
41+
pub fn is_excluded(&self, name: &str) -> bool {
42+
if let Some(excluded) = &self.exclude {
43+
return excluded.iter().any(|ident| ident == name);
44+
} else {
45+
false
46+
}
47+
}
48+
}
49+
50+
#[derive(Clone, Default, Debug)]
51+
pub(crate) struct VariantConfig {
52+
// #[enumcapsulate(exclude(…))]
53+
pub exclude: Option<Vec<syn::Ident>>,
54+
55+
// #[enumcapsulate(include(…))]
56+
pub include: Option<Vec<syn::Ident>>,
57+
58+
// #[enumcapsulate(field(… = …))]
59+
pub field: Option<VariantFieldConfig>,
60+
}
61+
62+
impl VariantConfig {
63+
pub fn is_excluded(&self, name: &str, config: &EnumConfig) -> bool {
64+
if self.is_excluded_explicitly(name) {
65+
assert!(!self.is_included_explicitly(name));
66+
return true;
67+
}
68+
69+
if self.is_included_explicitly(name) {
70+
return false;
71+
}
72+
73+
config.is_excluded(name)
74+
}
75+
76+
pub fn is_excluded_explicitly(&self, name: &str) -> bool {
77+
if let Some(excluded) = &self.exclude {
78+
if excluded.is_empty() {
79+
if let Some(included) = &self.include {
80+
return !included.iter().any(|ident| ident == name);
81+
} else {
82+
return true;
83+
}
84+
}
85+
86+
excluded.iter().any(|ident| ident == name)
87+
} else {
88+
false
89+
}
90+
}
91+
92+
pub fn is_included_explicitly(&self, name: &str) -> bool {
93+
if let Some(included) = &self.include {
94+
if included.is_empty() {
95+
if let Some(excluded) = &self.exclude {
96+
return !excluded.iter().any(|ident| ident == name);
97+
} else {
98+
return true;
99+
}
100+
}
101+
102+
included.iter().any(|ident| ident == name)
103+
} else {
104+
false
105+
}
106+
}
107+
}
108+
109+
pub(crate) fn config_for_enum_with_attrs(
110+
enum_attrs: &[syn::Attribute],
111+
) -> Result<EnumConfig, syn::Error> {
112+
let mut config = EnumConfig::default();
113+
114+
parse_enumcapsulate_attrs(enum_attrs, |meta| {
115+
if meta.path.is_ident(attr::EXCLUDE) {
116+
// #[enumcapsulate(exclude(…))]
117+
118+
let mut exclude = config.exclude.take().unwrap_or_default();
119+
exclude.extend(macro_idents_for_enum(&meta)?.into_iter());
120+
config.exclude = Some(exclude);
121+
} else {
122+
return Err(meta.error("unrecognized attribute"));
123+
}
124+
125+
Ok(())
126+
})?;
127+
128+
Ok(config)
129+
}
130+
131+
pub(crate) fn config_for_variant(variant: &syn::Variant) -> Result<VariantConfig, syn::Error> {
132+
let mut config = VariantConfig::default();
133+
134+
let fields = match &variant.fields {
135+
syn::Fields::Named(fields) => fields.named.iter().collect(),
136+
syn::Fields::Unnamed(fields) => fields.unnamed.iter().collect(),
137+
syn::Fields::Unit => vec![],
138+
};
139+
140+
parse_enumcapsulate_attrs(&variant.attrs, |meta| {
141+
if meta.path.is_ident(attr::EXCLUDE) {
142+
// #[enumcapsulate(exclude(…))]
143+
144+
let mut exclude = config.exclude.take().unwrap_or_default();
145+
let conflicting = config.include.as_deref().unwrap_or(&[]);
146+
147+
exclude.extend(macro_idents_for_variant(&meta, conflicting)?.into_iter());
148+
config.exclude = Some(exclude);
149+
} else if meta.path.is_ident(attr::INCLUDE) {
150+
// #[enumcapsulate(include(…))]
151+
152+
let mut include = config.include.take().unwrap_or_default();
153+
let conflicting = config.exclude.as_deref().unwrap_or(&[]);
154+
155+
include.extend(macro_idents_for_variant(&meta, conflicting)?.into_iter());
156+
config.include = Some(include);
157+
} else if meta.path.is_ident(attr::FIELD) {
158+
// #[enumcapsulate(field(…))]
159+
meta.parse_nested_meta(|meta| {
160+
if meta.path.is_ident(attr::NAME) {
161+
// #[enumcapsulate(field(name = "…"))]
162+
163+
if !matches!(&variant.fields, syn::Fields::Named(_)) {
164+
return Err(meta.error("no named fields in variant"));
165+
}
166+
167+
let lit: syn::LitStr = meta.value()?.parse()?;
168+
let name = lit.value();
169+
170+
let field_idents: Vec<_> = fields
171+
.iter()
172+
.filter_map(|&field| field.ident.as_ref())
173+
.collect();
174+
175+
if field_idents.is_empty() {
176+
return Err(meta.error("no named fields in variant"));
177+
}
178+
179+
let field_exists = field_idents.into_iter().any(|ident| ident == &name);
180+
181+
if !field_exists {
182+
return Err(meta.error("field not found in variant"));
183+
}
184+
185+
config.field = Some(VariantFieldConfig::Name(name));
186+
187+
Ok(())
188+
} else if meta.path.is_ident(attr::INDEX) {
189+
// #[enumcapsulate(field(index = …))]
190+
191+
if fields.is_empty() {
192+
return Err(meta.error("no fields in variant"));
193+
}
194+
195+
let lit: syn::LitInt = meta.value()?.parse()?;
196+
let index = lit.base10_parse()?;
197+
198+
if fields.len() <= index {
199+
return Err(meta.error("field index out of bounds"));
200+
}
201+
202+
config.field = Some(VariantFieldConfig::Index(index));
203+
204+
Ok(())
205+
} else {
206+
return Err(meta.error("unrecognized attribute"));
207+
}
208+
})?;
209+
} else {
210+
return Err(meta.error("unrecognized attribute"));
211+
}
212+
213+
Ok(())
214+
})?;
215+
216+
Ok(config)
217+
}
218+
219+
pub(crate) fn parse_enumcapsulate_attrs(
220+
attrs: &[syn::Attribute],
221+
logic: impl FnMut(syn::meta::ParseNestedMeta) -> Result<(), syn::Error>,
222+
) -> Result<(), syn::Error> {
223+
let mut logic = logic;
224+
225+
for attr in attrs {
226+
if !attr.path().is_ident(attr::NAMESPACE) {
227+
continue;
228+
}
229+
230+
// #[enumcapsulate(…)]
231+
attr.parse_nested_meta(&mut logic)?;
232+
}
233+
234+
Ok(())
235+
}
236+
237+
pub(crate) fn macro_idents_for_enum(
238+
meta: &syn::meta::ParseNestedMeta<'_>,
239+
) -> Result<Vec<syn::Ident>, syn::Error> {
240+
let idents = parse_idents_from_meta_list(meta)?;
241+
242+
let recognized = RECOGNIZED_ENUM_LEVEL_MACROS;
243+
ensure_only_recognized_ident_names(&idents, recognized)?;
244+
245+
Ok(idents)
246+
}
247+
248+
pub(crate) fn macro_idents_for_variant(
249+
meta: &syn::meta::ParseNestedMeta<'_>,
250+
conflict_list: &[syn::Ident],
251+
) -> Result<Vec<syn::Ident>, syn::Error> {
252+
let idents = parse_idents_from_meta_list(meta)?;
253+
254+
let recognized = RECOGNIZED_VARIANT_LEVEL_MACROS;
255+
ensure_only_recognized_ident_names(&idents, recognized)?;
256+
257+
ensure_no_conflicting_idents(&idents, conflict_list)?;
258+
259+
Ok(idents)
260+
}
261+
262+
pub(crate) fn ensure_only_recognized_ident_names(
263+
idents: &[syn::Ident],
264+
recognized: &[&str],
265+
) -> Result<(), syn::Error> {
266+
let mut error: Option<syn::Error> = None;
267+
268+
let unrecognized = idents
269+
.iter()
270+
.filter(|&ident| !recognized.iter().any(|recognized| ident == recognized));
271+
272+
for ident in unrecognized {
273+
let ident_err = syn::Error::new_spanned(ident, "unrecognized macro derive");
274+
if let Some(error) = error.as_mut() {
275+
error.combine(ident_err);
276+
} else {
277+
error = Some(ident_err)
278+
}
279+
}
280+
281+
if let Some(err) = error {
282+
return Err(err);
283+
}
284+
285+
Ok(())
286+
}
287+
288+
pub(crate) fn ensure_no_conflicting_idents(
289+
idents: &[syn::Ident],
290+
conflicting: &[syn::Ident],
291+
) -> Result<(), syn::Error> {
292+
let mut error: Option<syn::Error> = None;
293+
294+
let conflicting = idents
295+
.iter()
296+
.filter(|&ident| conflicting.iter().any(|conflicting| ident == conflicting));
297+
298+
for ident in conflicting {
299+
let ident_err = syn::Error::new_spanned(ident, "conflicting macro derive");
300+
if let Some(error) = error.as_mut() {
301+
error.combine(ident_err);
302+
} else {
303+
error = Some(ident_err)
304+
}
305+
}
306+
307+
if let Some(err) = error {
308+
return Err(err);
309+
}
310+
311+
Ok(())
312+
}
313+
314+
pub(crate) fn parse_idents_from_meta_list(
315+
meta: &syn::meta::ParseNestedMeta<'_>,
316+
) -> Result<Vec<syn::Ident>, syn::Error> {
317+
let mut idents = vec![];
318+
319+
let lookahead = meta.input.lookahead1();
320+
if lookahead.peek(syn::token::Paren) {
321+
let content;
322+
syn::parenthesized!(content in meta.input);
323+
let punctuated: Punctuated<syn::Ident, syn::Token![,]> =
324+
content.parse_terminated(syn::Ident::parse, syn::Token![,])?;
325+
326+
idents.extend(punctuated);
327+
}
328+
329+
Ok(idents)
330+
}
331+
332+
#[cfg(test)]
333+
mod tests;

0 commit comments

Comments
 (0)