1#![deny(warnings)]
2#![doc(test(attr(deny(warnings))))]
3#![doc(test(attr(allow(dead_code))))]
4#![doc(test(attr(allow(unused_variables))))]
5
6#![allow(clippy::type_complexity)]
7
8extern crate proc_macro;
9
10use proc_macro_crate::{FoundCrate, crate_name};
11use proc_macro_error::{abort, abort_call_site, proc_macro_error};
12use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
13use quote::{ToTokens, quote};
14use single::{self, Single};
15use syn::{AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, Fields, FieldsNamed};
16use syn::{FieldsUnnamed, GenericArgument, Index, Member, Path, PathArguments, PathSegment};
17use syn::{Token, TraitBound, TraitBoundModifier, Type, TypeParamBound, TypeReference, TypeTraitObject, parse};
18use syn::punctuated::Punctuated;
19use try_match::try_match;
20
21struct Parsed<T> {
22 value: T,
23 source: TokenStream,
24}
25
26impl<T> Parsed<T> {
27 fn new<S: ToTokens>(value:T, source: &S) -> Self {
28 Parsed { value, source: source.to_token_stream() }
29 }
30}
31
32enum Stop {
33 None,
34 Ignore,
35 Implicit,
36 Explicit,
37}
38
39const ERR_ILL_FORMED_STOP: &str = "\
40 ill-formed 'stop' attr, allowed forms are \
41 '#[stop]', \
42 '#[stop(ignore)]', \
43 '#[stop(implicit)]', and \
44 '#[stop(explicit)]'\
45";
46
47const ERR_DUPLICATED_STOP: &str = "\
48 duplicated 'stop' attribute\
49";
50
51const ERR_NO_STOP_EXPLICIT_HERE: &str = "\
52 '#[stop(explicit)]' is not allowed here\
53";
54
55const ERR_NO_STOP_IMPLICIT_HERE: &str = "\
56 '#[stop(implicit)]' is not allowed here\
57";
58
59const ERR_NO_STOP_IGNORE_HERE: &str = "\
60 '#[stop(ignore)]' is not allowed here\
61";
62
63const ERR_REDUNDANT_STOP_ON_STRUCT: &str = "\
64 redundant 'stop' attribute, use \
65 '#[stop(implicit)]' or \
66 '#[stop(explicit)] \
67 to manually choose fields selection rule\
68";
69
70const ERR_REDUNDANT_STOP: &str = "\
71 redundant '#[stop]' attribute, it is implicitly supposed here\
72";
73
74const ERR_REDUNDANT_STOP_IGNORE: &str = "\
75 redundant '#[stop(ignore)]' attribute, it is implicitly supposed here\
76";
77
78fn is_crate_attr(a: &Attribute) -> bool {
79 if a.path.leading_colon.is_some() || a.path.segments.trailing_punct() { return false; }
80 if let Ok(path) = a.path.segments.iter().single() {
81 path.arguments == PathArguments::None && path.ident == "_crate"
82 } else {
83 false
84 }
85}
86
87fn as_stop_attr(a: &Attribute) -> Option<Parsed<Stop>> {
88 if a.path.leading_colon.is_some() || a.path.segments.trailing_punct() { return None; }
89 let path = a.path.segments.iter().single().ok()?;
90 if path.arguments != PathArguments::None || path.ident != "stop" { return None; }
91 if a.tokens.is_empty() { return Some(Parsed::new(Stop::None, a)); }
92 Some((|| {
93 let token = a.tokens.clone().into_iter().single().ok()?;
94 let group = try_match!(token, TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis).ok()?;
95 let token = group.stream().into_iter().single().ok()?;
96 match token {
97 TokenTree::Ident(i) if i == "ignore" => Some(Parsed::new(Stop::Ignore, a)),
98 TokenTree::Ident(i) if i == "explicit" => Some(Parsed::new(Stop::Explicit, a)),
99 TokenTree::Ident(i) if i == "implicit" => Some(Parsed::new(Stop::Implicit, a)),
100 _ => None,
101 }
102 })().unwrap_or_else(|| abort!(a, ERR_ILL_FORMED_STOP)))
103}
104
105fn find_stop_attr(attrs: &[Attribute]) -> Option<Parsed<Stop>> {
106 let mut attrs = attrs.iter().filter_map(as_stop_attr);
107 let attr = attrs.next()?;
108 if let Some(duplicated_attr) = attrs.next() {
109 abort!(duplicated_attr.source, ERR_DUPLICATED_STOP);
110 }
111 Some(attr)
112}
113
114fn filter_implicit(attrs: &[Attribute]) -> bool {
115 match find_stop_attr(attrs) {
116 None => true,
117 Some(Parsed { value: Stop::None, source }) => abort!(source, ERR_REDUNDANT_STOP_ON_STRUCT),
118 Some(Parsed { value: Stop::Explicit, source }) => abort!(source, ERR_NO_STOP_EXPLICIT_HERE),
119 Some(Parsed { value: Stop::Implicit, source }) => abort!(source, ERR_NO_STOP_IMPLICIT_HERE),
120 Some(Parsed { value: Stop::Ignore, .. }) => false,
121 }
122}
123
124fn filter_explicit(attrs: &[Attribute]) -> bool {
125 match find_stop_attr(attrs) {
126 None => false,
127 Some(Parsed { value: Stop::None, .. }) => true,
128 Some(Parsed { value: Stop::Explicit, source }) => abort!(source, ERR_NO_STOP_EXPLICIT_HERE),
129 Some(Parsed { value: Stop::Implicit, source }) => abort!(source, ERR_NO_STOP_IMPLICIT_HERE),
130 Some(Parsed { value: Stop::Ignore, source }) => abort!(source, ERR_REDUNDANT_STOP_IGNORE),
131 }
132}
133
134fn select_filter(attrs: &[Attribute], named_fields: bool) -> fn(&[Attribute]) -> bool {
135 match find_stop_attr(attrs) {
136 None => if named_fields { filter_explicit } else { filter_implicit },
137 Some(Parsed { value: Stop::None, source }) => abort!(source, ERR_REDUNDANT_STOP),
138 Some(Parsed { value: Stop::Explicit, .. }) => filter_explicit,
139 Some(Parsed { value: Stop::Implicit, .. }) => filter_implicit,
140 Some(Parsed { value: Stop::Ignore, source }) => abort!(source, ERR_NO_STOP_IGNORE_HERE),
141 }
142}
143
144fn dyn_context_crate(use_crate: bool, path: impl IntoIterator<Item=PathSegment>) -> Path {
145 let c = crate_name("dyn-context").unwrap_or_else(|_| abort_call_site!("dyn-context dependency not found"));
146 let (leading_colon, name) = match &c {
147 FoundCrate::Itself => if use_crate { (false, "crate") } else { (true, "dyn_context") },
148 FoundCrate::Name(name) => (true, name.as_str()),
149 };
150 let mut segments = Punctuated::new();
151 segments.push(PathSegment { ident: Ident::new(name, Span::call_site()), arguments: PathArguments::None });
152 segments.extend(path);
153 Path {
154 leading_colon: if leading_colon { Some(Token)) } else { None },
155 segments
156 }
157}
158
159fn stop_trait(use_crate: bool) -> Path {
160 dyn_context_crate(use_crate, [
161 PathSegment { ident: Ident::new("Stop", Span::call_site()), arguments: PathArguments::None },
162 ])
163}
164
165fn state_trait(use_crate: bool) -> Path {
166 dyn_context_crate(use_crate, [
167 PathSegment { ident: Ident::new("State", Span::call_site()), arguments: PathArguments::None },
168 ])
169}
170
171fn iterate_struct_fields(
172 fields: Fields,
173) -> Vec<(Vec<Attribute>, Type, Member)> {
174 match fields {
175 Fields::Named(FieldsNamed { named, .. }) => named.into_iter()
176 .map(|f| (f.attrs, f.ty, Member::Named(f.ident.unwrap())))
177 .collect(),
178 Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => unnamed.into_iter()
179 .enumerate()
180 .map(|(i, f)| (f.attrs, f.ty, Member::Unnamed(Index {
181 index: i.try_into().unwrap(),
182 span: Span::call_site()
183 })))
184 .collect(),
185 Fields::Unit => Vec::new(),
186 }
187}
188
189fn call_stop_struct_fields(
190 use_crate: bool,
191 data: DataStruct,
192 state_var: &Ident,
193 filter: fn(&[Attribute]) -> bool
194) -> (TokenStream, TokenStream) {
195 let stop_trait = stop_trait(use_crate);
196 iterate_struct_fields(data.fields).into_iter()
197 .filter(|(attrs, _, _)| filter(attrs))
198 .map(|(_, ty, member)| (
199 quote! { <#ty as #stop_trait>::is_stopped(&self. #member) },
200 quote! { <#ty as #stop_trait>::stop(#state_var); },
201 ))
202 .fold((quote! { true }, TokenStream::new()), |
203 (is_stopped, mut stop),
204 (field_is_stopped, field_stop)
205 | (
206 quote! { #is_stopped && #field_is_stopped },
207 { stop.extend(field_stop); stop }
208 ))
209}
210
211#[proc_macro_error]
212#[proc_macro_derive(Stop, attributes(stop, _crate))]
213pub fn derive_stop(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
214 let DeriveInput { ident, data, attrs, generics, .. } = parse(item).unwrap();
215 let use_crate = attrs.iter().any(is_crate_attr);
216 let (g, r, w) = generics.split_for_impl();
217 let state_var = Ident::new("state", Span::mixed_site());
218 let (is_stopped, stop) = match data {
219 Data::Struct(data) => {
220 let filter = select_filter(&attrs, matches!(&data.fields, Fields::Named { .. }));
221 call_stop_struct_fields(use_crate, data, &state_var, filter)
222 },
223 Data::Enum(_) => abort_call_site!("'Stop' deriving is not supported for enums"),
224 Data::Union(_) => abort_call_site!("'Stop' deriving is not supported for unions"),
225 };
226 let stop_trait = stop_trait(use_crate);
227 let state_trait = state_trait(use_crate);
228 quote! {
229 impl #g #stop_trait #r for #ident #w {
230 fn is_stopped(&self) -> bool { #is_stopped }
231
232 fn stop(#state_var : &mut dyn #state_trait) { #stop }
233 }
234 }.into()
235}
236
237enum State {
238 None,
239 Part,
240}
241
242const ERR_ILL_FORMED_STATE: &str = "\
243 ill-formed 'state' attr, allowed forms are '#[state]', and '#[state(part)]'\
244";
245
246const ERR_DUPLICATED_STATE: &str = "\
247 duplicated 'state' attribute\
248";
249
250const ERR_REDUNDANT_STATE_ON_STRUCT: &str = "\
251 redundant 'state' attribute, use \
252 '#[state(part)]' \
253 to include the struct as itself part\
254";
255
256fn as_state_attr(a: &Attribute) -> Option<Parsed<State>> {
257 if a.path.leading_colon.is_some() || a.path.segments.trailing_punct() { return None; }
258 let path = a.path.segments.iter().single().ok()?;
259 if path.arguments != PathArguments::None || path.ident != "state" { return None; }
260 if a.tokens.is_empty() { return Some(Parsed::new(State::None, a)); }
261 Some((|| {
262 let token = a.tokens.clone().into_iter().single().ok()?;
263 let group = try_match!(token, TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis).ok()?;
264 let token = group.stream().into_iter().single().ok()?;
265 match token {
266 TokenTree::Ident(i) if i == "part" => Some(Parsed::new(State::Part, a)),
267 _ => None,
268 }
269 })().unwrap_or_else(|| abort!(a, ERR_ILL_FORMED_STATE)))
270}
271
272fn find_state_attr(attrs: &[Attribute]) -> Option<Parsed<State>> {
273 let mut attrs = attrs.iter().filter_map(as_state_attr);
274 let attr = attrs.next()?;
275 if let Some(duplicated_attr) = attrs.next() {
276 abort!(duplicated_attr.source, ERR_DUPLICATED_STATE);
277 }
278 Some(attr)
279}
280
281fn option_type(use_crate: bool, ty: Type) -> Path {
282 let mut args = Punctuated::new();
283 args.push(GenericArgument::Type(ty));
284 dyn_context_crate(use_crate, [
285 PathSegment {
286 ident: Ident::new("std_option_Option", Span::call_site()),
287 arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
288 colon2_token: None,
289 lt_token: Token),
290 gt_token: Token),
291 args
292 })
293 },
294 ])
295}
296
297fn any_trait(use_crate: bool) -> Path {
298 dyn_context_crate(use_crate, [
299 PathSegment { ident: Ident::new("std_any_Any", Span::call_site()), arguments: PathArguments::None },
300 ])
301}
302
303fn option_none(use_crate: bool) -> Path {
304 dyn_context_crate(use_crate, [
305 PathSegment { ident: Ident::new("std_option_Option", Span::call_site()), arguments: PathArguments::None },
306 PathSegment { ident: Ident::new("None", Span::call_site()), arguments: PathArguments::None },
307 ])
308}
309
310fn option_some(use_crate: bool) -> Path {
311 dyn_context_crate(use_crate, [
312 PathSegment { ident: Ident::new("std_option_Option", Span::call_site()), arguments: PathArguments::None },
313 PathSegment { ident: Ident::new("Some", Span::call_site()), arguments: PathArguments::None },
314 ])
315}
316
317fn type_id(use_crate: bool) -> Path {
318 dyn_context_crate(use_crate, [
319 PathSegment { ident: Ident::new("std_any_TypeId", Span::call_site()), arguments: PathArguments::None },
320 ])
321}
322
323fn option_dyn_any(use_crate: bool, mutable: bool) -> Path {
324 let mut bounds = Punctuated::new();
325 bounds.push(TypeParamBound::Trait(TraitBound {
326 paren_token: None,
327 modifier: TraitBoundModifier::None,
328 lifetimes: None,
329 path: any_trait(use_crate)
330 }));
331 option_type(use_crate, Type::Reference(TypeReference {
332 and_token: Token),
333 lifetime: None,
334 mutability: if mutable { Some(Token)) } else { None },
335 elem: Box::new(Type::TraitObject(TypeTraitObject {
336 dyn_token: Some(Token)),
337 bounds
338 }))
339 }))
340}
341
342fn call_state_struct_fields(
343 data: DataStruct,
344 attrs: &[Attribute],
345 ty_var: &Ident,
346) -> (TokenStream, TokenStream) {
347 let use_crate = attrs.iter().any(is_crate_attr);
348 let state_trait = state_trait(use_crate);
349 let option_none = option_none(use_crate);
350 let option_some = option_some(use_crate);
351 let type_id = type_id(use_crate);
352 let x_var = Ident::new("x", Span::mixed_site());
353 let (get_raw, get_mut_raw) = iterate_struct_fields(data.fields).into_iter()
354 .fold((quote! { #option_none }, quote! { #option_none }), |
355 (get_raw, get_mut_raw),
356 (attrs, ty, member)
357 | match find_state_attr(&attrs) {
358 None => (get_raw, get_mut_raw),
359 Some(Parsed { value: State::None, .. }) => (
360 quote! {
361 if let #option_some (#x_var) = <#ty as #state_trait>::get_raw(&self. #member, #ty_var) {
362 #option_some (#x_var)
363 } else {
364 #get_raw
365 }
366 },
367 quote! {
368 if let #option_some (#x_var) = <#ty as #state_trait>::get_mut_raw(&mut self. #member, #ty_var) {
369 #option_some (#x_var)
370 } else {
371 #get_mut_raw
372 }
373 },
374 ),
375 Some(Parsed { value: State::Part, .. }) => (
376 quote! { if #ty_var == <#type_id>::of::<#ty>() { #option_some (&self. #member) } else { #get_raw } },
377 quote! { if #ty_var == <#type_id>::of::<#ty>() { #option_some (&mut self. #member) } else { #get_mut_raw } },
378 ),
379 })
380 ;
381 match find_state_attr(attrs) {
382 None => (get_raw, get_mut_raw),
383 Some(Parsed { source, value: State::None }) => abort!(source, ERR_REDUNDANT_STATE_ON_STRUCT),
384 Some(Parsed { value: State::Part, .. }) => (
385 quote! { if #ty_var == <#type_id>::of::<Self>() { #option_some (self) } else { #get_raw } },
386 quote! { if #ty_var == <#type_id>::of::<Self>() { #option_some (self) } else { #get_mut_raw } },
387 ),
388 }
389}
390
391#[proc_macro_error]
392#[proc_macro_derive(State, attributes(state, _crate))]
393pub fn derive_state(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
394 let DeriveInput { ident, data, attrs, generics, .. } = parse(item).unwrap();
395 let use_crate = attrs.iter().any(is_crate_attr);
396 let (g, r, w) = generics.split_for_impl();
397 let ty_var = Ident::new("ty", Span::mixed_site());
398 let (get_raw, get_mut_raw) = match data {
399 Data::Struct(data) => call_state_struct_fields(data, &attrs, &ty_var),
400 Data::Enum(_) => abort_call_site!("'State' deriving is not supported for enums"),
401 Data::Union(_) => abort_call_site!("'State' deriving is not supported for unions"),
402 };
403 let state_trait = state_trait(use_crate);
404 let option_ref_dyn_any = option_dyn_any(use_crate, false);
405 let option_mut_dyn_any = option_dyn_any(use_crate, true);
406 let type_id = type_id(use_crate);
407 quote! {
408 impl #g #state_trait #r for #ident #w {
409 fn get_raw(&self, #ty_var : #type_id) -> #option_ref_dyn_any { #get_raw }
410
411 fn get_mut_raw(&mut self, #ty_var : #type_id) -> #option_mut_dyn_any { #get_mut_raw }
412 }
413 }.into()
414}