lin_state_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{quote, quote_spanned};
3use syn::{
4    parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, GenericParam, Generics,
5    Index,
6};
7
8#[proc_macro_derive(Resource)]
9pub fn derive_resource(input: TokenStream) -> TokenStream {
10    let input = parse_macro_input!(input as DeriveInput);
11
12    let name = input.ident;
13
14    // Add a bound `T: Resource` to every type parameter T.
15    let generics = add_trait_bounds(input.generics);
16    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
17
18    // Generate an expression to be used in clone_state method and
19    // an expression to be used in set_cleanup_enabled method.
20    let cloned_state = clone_state_expr(&input.data);
21    let cleanup_enabled = set_cleanup_enabled_expr(&input.data);
22
23    let expanded = quote! {
24        // The generated impl.
25        impl #impl_generics lin_state::Resource for #name #ty_generics #where_clause {
26            unsafe fn clone_state(&self) -> Self {
27                #cloned_state
28            }
29
30            unsafe fn set_cleanup_enabled(&mut self, cleanup_enabled: bool) {
31                #cleanup_enabled
32            }
33        }
34    };
35
36    TokenStream::from(expanded)
37}
38
39// Add a bound `T: Resource` to every type parameter T.
40fn add_trait_bounds(mut generics: Generics) -> Generics {
41    for param in &mut generics.params {
42        if let GenericParam::Type(ref mut type_param) = *param {
43            type_param.bounds.push(parse_quote!(lin_state::Resource));
44        }
45    }
46    generics
47}
48
49// Generate an expression to clone the resource state.
50fn clone_state_expr(data: &Data) -> proc_macro2::TokenStream {
51    match *data {
52        Data::Struct(ref data) => match data.fields {
53            syn::Fields::Named(ref fields) => {
54                // Expand to expression like:
55                // ```
56                // Self { a: self.a.clone_state(), b: self.b.clone_state(), }
57                // ```
58                let recurse = fields.named.iter().map(|f| {
59                    let name = &f.ident;
60                    quote_spanned!(f.span() => #name: self.#name.clone_state())
61                });
62
63                quote!(Self { #(#recurse),* })
64            }
65            syn::Fields::Unnamed(ref fields) => {
66                // Expand to expression like:
67                // ```
68                // Self(self.0.clone_state(), self.1.clone_state())
69                // ```
70                let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| {
71                    let index = Index::from(i);
72                    quote_spanned!(f.span() => self.#index.clone_state())
73                });
74
75                quote!(Self(#(#recurse),*))
76            }
77            // Unit structs cannot implement the Resource trait, since we can not change if cleanup
78            // is enabled. To change if cleanup is enabled, at least one field must be present in the struct.
79            syn::Fields::Unit => panic!("Unit structs are not supported"),
80        },
81        Data::Enum(_) | Data::Union(_) => unimplemented!(),
82    }
83}
84
85// Generate an expression to set the cleanup_enabled flag.
86fn set_cleanup_enabled_expr(data: &Data) -> proc_macro2::TokenStream {
87    match *data {
88        Data::Struct(ref data) => match data.fields {
89            syn::Fields::Named(ref fields) => {
90                // Expand to expression like:
91                // ```
92                // self.a.set_cleanup_enabled(cleanup_enabled);
93                // self.b.set_cleanup_enabled(cleanup_enabled);
94                // ```
95                let recourse = fields.named.iter().map(|f| {
96                    let name = &f.ident;
97                    quote_spanned!(f.span() => self.#name.set_cleanup_enabled(cleanup_enabled);)
98                });
99
100                quote!(#(#recourse)*)
101            }
102            syn::Fields::Unnamed(ref fields) => {
103                // Expand to expression like:
104                // ```
105                // self.0.set_cleanup_enabled(cleanup_enabled);
106                // self.1.set_cleanup_enabled(cleanup_enabled);
107                // ```
108                let recourse = fields.unnamed.iter().enumerate().map(|(i, f)| {
109                    let index = Index::from(i);
110                    quote_spanned!(f.span() => self.#index.set_cleanup_enabled(cleanup_enabled);)
111                });
112
113                quote!(#(#recourse)*)
114            }
115            // Unit structs cannot implement the Resource trait, since we can not change if cleanup
116            // is enabled. To change if cleanup is enabled, at least one field must be present in the struct.
117            syn::Fields::Unit => panic!("Unit structs are not supported"),
118        },
119        Data::Enum(_) | Data::Union(_) => unimplemented!(),
120    }
121}