libatk_derive/
lib.rs

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