/
macro.rs
175 lines (152 loc) · 5.01 KB
/
macro.rs
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use proc_macro::TokenStream;
use std::error::Error;
use string_cases::StringCasesExt;
use syn_helpers::{
derive_trait,
proc_macro2::{Ident, Span},
quote,
syn::{
self, parse_macro_input, parse_quote, DeriveInput, Stmt, __private::quote::format_ident,
parse::Parse,
},
Constructable, FieldMut, HasAttributes, NamedOrUnnamedFieldMut, Trait, TraitItem,
};
/// On the top structure
const VISIT_SELF_NAME: &str = "visit_self";
/// Per field modifiers
const VISIT_SKIP_NAME: &str = "visit_skip_field";
/// Add to chain. Can be on item or a field
const VISIT_WITH_CHAIN_NAME: &str = "visit_with_chain";
/// Usage #[derive(Visitable)]
#[proc_macro_derive(Visitable, attributes(visit_self, visit_skip_field, visit_custom_visit))]
pub fn generate_visit_implementation(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let visit_item = TraitItem::new_method(
Ident::new("visit", Span::call_site()),
Some(vec![parse_quote!(TData)]),
syn_helpers::TypeOfSelf::Reference,
vec![
parse_quote!(visitors: &mut (impl crate::visiting::VisitorReceiver<TData> + ?Sized)),
parse_quote!(data: &mut TData),
parse_quote!(options: &crate::VisitOptions),
parse_quote!(chain: &mut ::temporary_annex::Annex<crate::visiting::Chain>),
],
None,
|item| generated_visit_item(item, VisitType::Immutable),
);
let visit_mut_item = TraitItem::new_method(
Ident::new("visit_mut", Span::call_site()),
Some(vec![parse_quote!(TData)]),
syn_helpers::TypeOfSelf::MutableReference,
vec![
parse_quote!(visitors: &mut (impl crate::visiting::VisitorMutReceiver<TData> + ?Sized)),
parse_quote!(data: &mut TData),
parse_quote!(options: &crate::VisitOptions),
parse_quote!(chain: &mut ::temporary_annex::Annex<crate::visiting::Chain>),
],
None,
|item| generated_visit_item(item, VisitType::Mutable),
);
let visitable_trait = Trait {
name: parse_quote!(crate::visiting::Visitable),
generic_parameters: None,
items: vec![visit_item, visit_mut_item],
};
let output = derive_trait(input, visitable_trait);
output.into()
}
#[derive(Clone, Copy)]
enum VisitType {
Immutable,
Mutable,
}
fn generated_visit_item(
mut item: syn_helpers::Item,
visit_type: VisitType,
) -> Result<Vec<Stmt>, Box<dyn Error>> {
let attributes = item.structure.get_attributes();
let visit_self = attributes.iter().find_map(|attr| {
attr.path().is_ident(VISIT_SELF_NAME).then_some({
let mut ident = None::<Ident>;
let res = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("under") {
let value = meta.value()?;
ident = value.parse()?;
Ok(())
} else {
Err(meta.error("expected `under=...`"))
}
});
if res.is_ok() {
Some(ident.unwrap())
} else {
// TODO
None
}
})
});
let visit_with_chain: Option<syn::Expr> = attributes
.iter()
.find_map(|attr| {
attr.path().is_ident(VISIT_WITH_CHAIN_NAME).then_some(attr.parse_args().ok())
})
.flatten();
let mut lines = Vec::new();
if let Some(expr_tokens) = visit_with_chain {
lines.push(parse_quote!( let mut chain = &mut chain.push_annex(#expr_tokens); ))
}
if let Some(under) = visit_self {
let mut_postfix =
matches!(visit_type, VisitType::Mutable).then_some("_mut").unwrap_or_default();
if let Some(under) = under {
let func_name = format_ident!("visit_{}{}", under, mut_postfix);
lines.push(parse_quote!(visitors.#func_name(self.into(), data, chain); ))
} else {
let struct_name_as_snake_case = &item.structure.get_name().to_string().to_snake_case();
let func_name = format_ident!("visit_{}{}", struct_name_as_snake_case, mut_postfix);
lines.push(parse_quote!( visitors.#func_name(self, data, chain); ))
}
}
let mut field_lines = item.map_constructable(|mut constructable| {
Ok(constructable
.get_fields_mut()
.fields_iterator_mut()
.flat_map(|mut field: NamedOrUnnamedFieldMut| -> Option<Stmt> {
let attributes = field.get_attributes();
let skip_field_attr =
attributes.iter().find(|attr| attr.path().is_ident(VISIT_SKIP_NAME));
// TODO maybe?
// // None == unconditional
// let _skip_field_expression: Option<Expr> =
// skip_field_attr.as_ref().map(|attr| attr.bracket_token);
let visit_with_chain = attributes.iter().find_map(|attr| {
// TODO error rather than flatten
attr.path()
.is_ident(VISIT_WITH_CHAIN_NAME)
.then_some(attr.parse_args_with(syn::Expr::parse).ok())
.flatten()
});
let chain = if let Some(expr_tokens) = visit_with_chain {
quote!(&mut chain.push_annex(#expr_tokens))
} else {
quote!(chain)
};
if skip_field_attr.is_none() {
let reference = field.get_reference();
Some(match visit_type {
VisitType::Immutable => parse_quote! {
crate::Visitable::visit(#reference, visitors, data, options, #chain);
},
VisitType::Mutable => parse_quote! {
crate::Visitable::visit_mut(#reference, visitors, data, options, #chain);
},
})
} else {
None
}
})
.collect::<Vec<_>>())
})?;
lines.append(&mut field_lines);
Ok(lines)
}