dyn_context_macro/
lib.rs

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![::](Span::call_site())) } 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![<](Span::call_site()),
290                gt_token: Token![>](Span::call_site()),
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![&](Span::call_site()),
333        lifetime: None,
334        mutability: if mutable { Some(Token![mut](Span::call_site())) } else { None },
335        elem: Box::new(Type::TraitObject(TypeTraitObject {
336            dyn_token: Some(Token![dyn](Span::call_site())),
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}