libatk_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4    parse_macro_input, AngleBracketedGenericArguments, DeriveInput, FnArg, GenericArgument, Ident,
5    ImplItem, ItemImpl, Pat, PathArguments, Type, TypePath,
6};
7
8#[proc_macro_derive(Command)]
9pub fn command_trait(input: TokenStream) -> TokenStream {
10    let ast = parse_macro_input!(input as DeriveInput);
11
12    let name = &ast.ident;
13    quote! {
14        impl CommandDescriptor for #name {}
15    }
16    .into()
17}
18
19#[proc_macro_attribute]
20pub fn command_extension(_attr: TokenStream, item: TokenStream) -> TokenStream {
21    let input = parse_macro_input!(item as ItemImpl);
22
23    let target_type = &*input.self_ty;
24
25    // Expect target_type looks like Command<Something>
26    let (_, generic_arg_type) = match target_type {
27        Type::Path(TypePath { path, .. }) => {
28            let first_segment = path.segments.first().expect("Expected a path segment");
29            if first_segment.ident != "Command" {
30                return syn::Error::new_spanned(
31                    target_type,
32                    "command_extension only works on impl blocks for Command<T>",
33                )
34                .to_compile_error()
35                .into();
36            }
37            // Extract the single generic argument T from Command<T>.
38            match &first_segment.arguments {
39                PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) => {
40                    if args.len() != 1 {
41                        return syn::Error::new_spanned(
42                            &first_segment.arguments,
43                            "Expected exactly one generic argument",
44                        )
45                        .to_compile_error()
46                        .into();
47                    }
48                    let generic_arg = args.first().unwrap();
49                    match generic_arg {
50                        GenericArgument::Type(ty) => (first_segment.ident.clone(), ty.clone()),
51                        _ => {
52                            return syn::Error::new_spanned(
53                                generic_arg,
54                                "Expected a type as the generic argument",
55                            )
56                            .to_compile_error()
57                            .into();
58                        }
59                    }
60                }
61                _ => {
62                    return syn::Error::new_spanned(
63                        &first_segment.arguments,
64                        "Expected angle bracketed generic arguments",
65                    )
66                    .to_compile_error()
67                    .into();
68                }
69            }
70        }
71        _ => {
72            return syn::Error::new_spanned(
73                target_type,
74                "command_extension can only be applied to impl blocks for Command<T>",
75            )
76            .to_compile_error()
77            .into();
78        }
79    };
80
81    let inner_type_ident = match generic_arg_type {
82        Type::Path(TypePath { ref path, .. }) => path
83            .segments
84            .last()
85            .expect("Expected at least one segment in the generic type")
86            .ident
87            .clone(),
88        _ => {
89            return syn::Error::new_spanned(
90                generic_arg_type,
91                "Expected a simple type for the generic parameter",
92            )
93            .to_compile_error()
94            .into();
95        }
96    };
97
98    // Build names for the two traits:
99    // For Command<T> extension trait (the original full set, including both getters and setters).
100    let trait_name_str = format!("{}Ext", inner_type_ident);
101    let trait_ident = Ident::new(&trait_name_str, proc_macro2::Span::call_site());
102    // For the builder extension trait (only for setters). We’ll call it CommandNameBuilderExt.
103    let builder_trait_name_str = format!("{}BuilderExt", inner_type_ident);
104    let builder_trait_ident = Ident::new(&builder_trait_name_str, proc_macro2::Span::call_site());
105
106    // Prepare to collect the methods for the two generated impls.
107    let mut cmd_trait_methods = Vec::new();
108    let mut cmd_impl_methods = Vec::new();
109
110    let mut builder_trait_methods = Vec::new();
111    let mut builder_impl_methods = Vec::new();
112
113    // For each function in the input impl block…
114    for item in input.items.iter() {
115        if let ImplItem::Fn(method) = item {
116            let sig = &method.sig;
117            let attrs = &method.attrs;
118
119            // Add the method to the command trait (the “full” trait) as-is.
120            let trait_method = quote! {
121                #(#attrs)*
122                #sig;
123            };
124            cmd_trait_methods.push(trait_method);
125
126            let block = &method.block;
127            let impl_method = quote! {
128                #(#attrs)*
129                #sig #block
130            };
131            cmd_impl_methods.push(impl_method);
132
133            // Now if the method name begins with "set_", generate the corresponding builder method.
134            let method_name = sig.ident.to_string();
135            if let Some(stripped) = method_name.strip_prefix("set_") {
136                // New (builder) method name: drop the set_ prefix.
137                let builder_method_ident = Ident::new(stripped, sig.ident.span());
138
139                // The builder method’s signature becomes:
140                //     fn <builder_method_ident>(mut self, <args from the original> ) -> Self;
141                // The original setter is assumed to have a receiver (e.g. &mut self) plus one or more parameters.
142                // We remove the original receiver and use "mut self" instead.
143                let mut builder_inputs = Vec::new();
144                let mut arg_idents = Vec::new();
145                // iterate over inputs skipping the receiver.
146                for input in sig.inputs.iter().skip(1) {
147                    builder_inputs.push(input);
148                    // Also extract the identifier from each argument so we can pass it on.
149                    if let FnArg::Typed(pat_type) = input {
150                        // We expect the pat to be a simple identifier.
151                        if let Pat::Ident(pat_ident) = *pat_type.pat.clone() {
152                            arg_idents.push(pat_ident.ident);
153                        }
154                    }
155                }
156
157                // Build the builder method signature. We want something like:
158                //    fn rgb_lighting_effects(mut self, <params>) -> Self;
159                let builder_sig = quote! {
160                    fn #builder_method_ident(self, #(#builder_inputs),* ) -> Self;
161                };
162
163                builder_trait_methods.push(builder_sig);
164
165                // Inside the loop over methods, before generating the builder impl:
166                let setter_ident = sig.ident.clone();
167
168                // Then generate the builder impl using the captured setter_ident:
169                let builder_impl = quote! {
170                    fn #builder_method_ident(mut self, #(#builder_inputs),* ) -> Self {
171                        self.command.#setter_ident( #(#arg_idents),* );
172                        self
173                    }
174                };
175
176                builder_impl_methods.push(builder_impl);
177            }
178        }
179    }
180
181    // Build the command extension trait definition and its impl block.
182    let cmd_trait_def = quote! {
183        pub trait #trait_ident {
184            #(#cmd_trait_methods)*
185        }
186    };
187
188    let cmd_impl_block = quote! {
189        impl #trait_ident for #target_type {
190            #(#cmd_impl_methods)*
191        }
192    };
193
194    // Build the builder extension trait definition.
195    let builder_trait_def = quote! {
196        pub trait #builder_trait_ident {
197            #(#builder_trait_methods)*
198        }
199    };
200
201    // Build the builder target type: CommandBuilder<T>
202    let builder_target = quote! { CommandBuilder<#generic_arg_type> };
203
204    let builder_impl_block = quote! {
205        impl #builder_trait_ident for #builder_target {
206            #(#builder_impl_methods)*
207        }
208    };
209
210    // Finally, put everything together. The output contains:
211    //  - the original command extension trait and impl for Command<T>
212    //  - the builder extension trait and impl for CommandBuilder<T>
213    let output = quote! {
214        #cmd_trait_def
215        #cmd_impl_block
216
217        #builder_trait_def
218        #builder_impl_block
219    };
220
221    output.into()
222}