Skip to main content

tusks_lib/codegen/handle_matches/arms/
function.rs

1use proc_macro2::{Span, TokenStream};
2use quote::{format_ident, quote};
3
4use crate::codegen::util::enum_util::convert_function_to_enum_variant;
5
6use crate::{TusksModule, models::Tusk};
7
8impl TusksModule {
9    /// Coordinates the construction of a match arm for a function.
10    /// Creates all necessary parts and combines them into the final code.
11    /// 
12    /// # Examples
13    /// 
14    /// For a function `fn my_func(arg1: String, arg2: i32)` this generates:
15    /// ```rust
16    /// Some(cli::Commands::MyFunction { arg1: p1, arg2: p2 }) => {
17    ///     super::my_func(p1.clone(), p2.clone());
18    /// }
19    /// ```
20    /// 
21    /// For a function with parameters `fn my_func(params: &Parameters, arg1: String)`
22    /// this generates:
23    /// ```rust
24    /// Some(cli::Commands::MyFunction { arg1: p1 }) => {
25    ///     super::my_func(&parameters, p1.clone());
26    /// }
27    /// ```
28    pub fn build_function_match_arm(
29        &self,
30        tusk: &Tusk,
31        cli_path: &TokenStream,
32        path: &[&str]
33    ) -> TokenStream {
34        let variant_ident = convert_function_to_enum_variant(&tusk.func.sig.ident);
35        let pattern_bindings = self.build_pattern_bindings(tusk);
36        let pattern_fields = self.build_pattern_fields(&pattern_bindings);
37        let function_call = self.build_function_call(
38            tusk,
39            &pattern_bindings,
40            path,
41            false,
42            false
43        );
44
45        quote! {
46            Some(#cli_path::Commands::#variant_ident { #(#pattern_fields),* }) => {
47                #function_call
48            }
49        }
50    }
51
52    pub fn build_default_function_match_arm(
53        &self,
54        tusk: &Tusk,
55        path: &[&str],
56        is_external_subcommand_case: bool
57    ) -> TokenStream {
58        let pattern_bindings = self.build_pattern_bindings(tusk);
59        let function_call = self.build_function_call(
60            tusk,
61            &pattern_bindings,
62            path,
63            true,
64            is_external_subcommand_case
65        );
66
67        quote! {
68            None => {
69                #function_call
70            }
71        }
72    }
73
74    pub fn build_external_subcommand_match_arm(&self, tusk: &Tusk, path: &[&str]) -> TokenStream
75    {
76        let pattern_bindings = self.build_pattern_bindings(tusk);
77        let function_call = self.build_function_call(
78            tusk,
79            &pattern_bindings,
80            path,
81            false,
82            true
83        );
84
85        let path_tokens = path.iter().map(|segment| {
86            let ident = format_ident!("{}", segment);
87            quote! { #ident:: }
88        });
89
90        quote! {
91            Some(cli::#(#path_tokens)*Commands::ClapExternalSubcommand(external_subcommand_args)) => {
92                #function_call
93            }
94        }
95    }
96
97    /// Creates the function call with proper arguments and path, always returning Option<i32>
98    fn build_function_call(
99        &self,
100        tusk: &Tusk,
101        pattern_bindings: &[(syn::Ident, syn::Ident)],
102        path: &[&str],
103        is_default_case: bool,
104        is_external_subcommand_case: bool
105    ) -> TokenStream {
106        let func_args = self.build_function_arguments(
107            tusk,
108            pattern_bindings,
109            is_default_case,
110            is_external_subcommand_case
111        );
112        let func_path = self.build_function_path(tusk, path);
113        
114        match &tusk.func.sig.output {
115            syn::ReturnType::Default => {
116                // Function returns () - call it and return None
117                quote! { #func_path(#(#func_args),*); None }
118            }
119            syn::ReturnType::Type(_, ty) => {
120                if Tusk::is_u8_type(ty) {
121                    // Function returns u8 - call it and wrap in Some
122                    quote! { Some(#func_path(#(#func_args),*)) }
123                } else if Tusk::is_option_u8_type(ty) {
124                    // Function returns Option<u8> - call it and return as is
125                    quote! { #func_path(#(#func_args),*) }
126                } else {
127                    // This should not happen due to validation
128                    quote! { None }
129                }
130            }
131        }
132    }
133
134    /// Creates bindings for function parameters (p1, p2, p3, ...).
135    /// Skips the first parameter if it's &Parameters.
136    /// 
137    /// # Examples
138    /// 
139    /// For function `fn my_func(params: &Params, arg1: String, arg2: i32)`:
140    /// ```rust
141    /// [("arg1", "p1"), ("arg2", "p2")]
142    /// ```
143    fn build_pattern_bindings(&self, tusk: &Tusk) -> Vec<(syn::Ident, syn::Ident)> {
144        let has_params_arg = self.tusk_has_parameters_arg(tusk);
145        let skip = if has_params_arg { 1 } else { 0 };
146
147        let mut pattern_bindings = Vec::new();
148        let mut param_counter = 1;
149
150        for param in tusk.func.sig.inputs.iter().skip(skip) {
151            if let syn::FnArg::Typed(pat_type) = param {
152                if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
153                    let field_name = &pat_ident.ident;
154                    let binding_name = syn::Ident::new(
155                        &format!("p{}", param_counter),
156                        Span::call_site()
157                    );
158                    pattern_bindings.push((field_name.clone(), binding_name.clone()));
159                    param_counter += 1;
160                }
161            }
162        }
163
164        pattern_bindings
165    }
166
167    /// Creates the fields for the match arm pattern.
168    /// 
169    /// # Examples
170    /// 
171    /// For bindings `[("arg1", "p1"), ("arg2", "p2")]`:
172    /// ```rust
173    /// ["arg1: p1", "arg2: p2"]
174    /// ```
175    pub fn build_pattern_fields(
176        &self,
177        pattern_bindings: &[(syn::Ident, syn::Ident)]
178    ) -> Vec<TokenStream> {
179        pattern_bindings.iter()
180            .filter(|(field_name, _)| {
181                let field_name_str = field_name.to_string();
182                field_name_str != "_phantom_lifetime_marker"
183            })
184            .map(|(field_name, binding_name)| {
185                quote! { #field_name: #binding_name }
186            })
187            .collect()
188    }
189
190    /// Creates the arguments for the function call.
191    /// Adds &parameters if present, followed by the bound parameters.
192    /// 
193    /// # Examples
194    /// 
195    /// For function with parameters:
196    /// ```rust
197    /// [&parameters, p1.clone(), p2.clone()]
198    /// ```
199    fn build_function_arguments(
200        &self,
201        tusk: &Tusk,
202        pattern_bindings: &[(syn::Ident, syn::Ident)],
203        is_default_case: bool,
204        is_external_subcommand_case: bool
205    ) -> Vec<TokenStream> {
206        let has_params_arg = self.tusk_has_parameters_arg(tusk);
207        let mut func_args = Vec::new();
208
209        let mut number_of_non_params_args = tusk.func.sig.inputs.len();
210        if has_params_arg {
211            func_args.push(quote! { &parameters });
212            number_of_non_params_args -= 1;
213        }
214
215        if is_default_case {
216            if is_external_subcommand_case {
217                func_args.push(quote! { Vec::new() });
218            }
219            return func_args;
220        }
221
222        if is_external_subcommand_case && number_of_non_params_args > 0 {
223            func_args.push(quote! { external_subcommand_args.clone() });
224            return func_args;
225        }
226
227        for (_, binding_name) in pattern_bindings {
228            func_args.push(quote! { #binding_name.clone() });
229        }
230
231        func_args
232    }
233
234    /// Creates the full path to the function.
235    /// Handles both local and nested paths.
236    /// 
237    /// # Examples
238    /// 
239    /// - Local path: `super::my_function`
240    /// - Nested path: `super::module1::module2::my_function`
241    fn build_function_path(&self, tusk: &Tusk, path: &[&str]) -> TokenStream {
242        let func_name = &tusk.func.sig.ident;
243
244        if path.is_empty() {
245            quote! { super::#func_name }
246        } else {
247            let path_idents: Vec<_> = path.iter()
248                .map(|p| syn::Ident::new(p, Span::call_site()))
249                .collect();
250            quote! { super::#(#path_idents)::*::#func_name }
251        }
252    }
253}