1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#![doc = include_str!("../README.md")]
mod build;
mod derive;
mod r#enum;
mod extractor;
mod iter;
mod param;
use std::collections::HashMap;
use derive::Derive;
use heck::ToSnakeCase;
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use r#enum::Enum;
use syn::{
parse_macro_input, Attribute, AttributeArgs, DeriveInput, Field, Meta, NestedMeta, Path, Type,
};
const SUBENUM: &str = "subenum";
fn snake_case(field: &Field) -> Ident {
let ident = field.ident.as_ref().unwrap_or_else(|| {
match &field.ty {
Type::Path(path) => path.path.get_ident().unwrap(),
_ => unimplemented!(),
}
});
Ident::new(&ident.to_string().to_snake_case(), ident.span())
}
fn sanitize_input(input: &mut DeriveInput) {
let data = match input.data {
syn::Data::Enum(ref mut data) => data,
_ => panic!("SubEnum may only be used on enums."),
};
for variant in data.variants.iter_mut() {
let mut i = 0;
while i < variant.attrs.len() {
if variant.attrs[i].path.is_ident(SUBENUM) {
variant.attrs.remove(i);
} else {
i += 1;
}
}
}
}
fn attribute_paths(attr: &Attribute) -> impl Iterator<Item = Path> {
let meta = attr.parse_meta().unwrap();
let nested = match meta {
Meta::List(list) => list.nested,
_ => unimplemented!(),
};
nested.into_iter().map(|nested| match nested {
NestedMeta::Meta(Meta::Path(path)) => path,
_ => unimplemented!(),
})
}
fn build_enum_map(args: AttributeArgs, derives: &[Derive]) -> HashMap<Ident, Enum> {
let err = "subenum must be called with a list of identifiers, like `#[subenum(EnumA, EnumB)]`";
args.into_iter()
.map(|nested| match nested {
NestedMeta::Meta(meta) => meta,
NestedMeta::Lit(_) => panic!("{err}"),
})
.map(|meta| match meta {
Meta::Path(path) => path,
_ => panic!("{err}"),
})
.map(|path| path.get_ident().expect(err).to_owned())
.map(|ident| (ident.clone(), Enum::new(ident, derives.to_owned())))
.collect()
}
#[proc_macro_attribute]
pub fn subenum(args: TokenStream, tokens: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as AttributeArgs);
let mut input = parse_macro_input!(tokens as DeriveInput);
let data = match input.data {
syn::Data::Enum(ref data) => data,
_ => panic!("subenum may only be used on enums."),
};
let mut derives = Vec::new();
for attr in &input.attrs {
if attr.path.is_ident("derive") {
for path in attribute_paths(attr) {
if path.is_ident("PartialEq") {
derives.push(Derive::PartialEq);
}
}
}
}
let mut enums = build_enum_map(args, &derives);
for variant in &data.variants {
for attribute in &variant.attrs {
if attribute.path.is_ident(SUBENUM) {
for path in attribute_paths(attribute) {
let ident = path.get_ident().unwrap();
let mut var = variant.clone();
var.attrs = var
.attrs
.iter()
.cloned()
.filter(|attr| attribute != attr)
.collect();
let e = enums
.get_mut(ident)
.expect("All enums to be created must be declared at the top-level subenum attribute");
e.variants.push(var);
}
}
}
}
for e in enums.values_mut() {
e.compute_generics(&input.generics);
}
let enums: Vec<_> = enums.into_values().map(|e| e.build(&input, data)).collect();
sanitize_input(&mut input);
quote!(
#input
#(#enums)*
)
.into()
}