tusks_lib/codegen/handle_matches/arms/
function.rs

1use proc_macro2::{Span, TokenStream};
2use quote::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        quote! {
86            Some(cli::Commands::ClapExternalSubcommand(external_subcommand_args)) => {
87                #function_call
88            }
89        }
90    }
91
92    /// Creates the function call with proper arguments and path, always returning Option<i32>
93    fn build_function_call(
94        &self,
95        tusk: &Tusk,
96        pattern_bindings: &[(syn::Ident, syn::Ident)],
97        path: &[&str],
98        is_default_case: bool,
99        is_external_subcommand_case: bool
100    ) -> TokenStream {
101        let func_args = self.build_function_arguments(
102            tusk,
103            pattern_bindings,
104            is_default_case,
105            is_external_subcommand_case
106        );
107        let func_path = self.build_function_path(tusk, path);
108        
109        match &tusk.func.sig.output {
110            syn::ReturnType::Default => {
111                // Function returns () - call it and return None
112                quote! { #func_path(#(#func_args),*); None }
113            }
114            syn::ReturnType::Type(_, ty) => {
115                if Tusk::is_u8_type(ty) {
116                    // Function returns u8 - call it and wrap in Some
117                    quote! { Some(#func_path(#(#func_args),*)) }
118                } else if Tusk::is_option_u8_type(ty) {
119                    // Function returns Option<u8> - call it and return as is
120                    quote! { #func_path(#(#func_args),*) }
121                } else {
122                    // This should not happen due to validation
123                    quote! { None }
124                }
125            }
126        }
127    }
128
129    /// Creates bindings for function parameters (p1, p2, p3, ...).
130    /// Skips the first parameter if it's &Parameters.
131    /// 
132    /// # Examples
133    /// 
134    /// For function `fn my_func(params: &Params, arg1: String, arg2: i32)`:
135    /// ```rust
136    /// [("arg1", "p1"), ("arg2", "p2")]
137    /// ```
138    fn build_pattern_bindings(&self, tusk: &Tusk) -> Vec<(syn::Ident, syn::Ident)> {
139        let has_params_arg = self.tusk_has_parameters_arg(tusk);
140        let skip = if has_params_arg { 1 } else { 0 };
141
142        let mut pattern_bindings = Vec::new();
143        let mut param_counter = 1;
144
145        for param in tusk.func.sig.inputs.iter().skip(skip) {
146            if let syn::FnArg::Typed(pat_type) = param {
147                if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
148                    let field_name = &pat_ident.ident;
149                    let binding_name = syn::Ident::new(
150                        &format!("p{}", param_counter),
151                        Span::call_site()
152                    );
153                    pattern_bindings.push((field_name.clone(), binding_name.clone()));
154                    param_counter += 1;
155                }
156            }
157        }
158
159        pattern_bindings
160    }
161
162    /// Creates the fields for the match arm pattern.
163    /// 
164    /// # Examples
165    /// 
166    /// For bindings `[("arg1", "p1"), ("arg2", "p2")]`:
167    /// ```rust
168    /// ["arg1: p1", "arg2: p2"]
169    /// ```
170    pub fn build_pattern_fields(
171        &self,
172        pattern_bindings: &[(syn::Ident, syn::Ident)]
173    ) -> Vec<TokenStream> {
174        pattern_bindings.iter()
175            .filter(|(field_name, _)| {
176                let field_name_str = field_name.to_string();
177                field_name_str != "_phantom_lifetime_marker"
178            })
179            .map(|(field_name, binding_name)| {
180                quote! { #field_name: #binding_name }
181            })
182            .collect()
183    }
184
185    /// Creates the arguments for the function call.
186    /// Adds &parameters if present, followed by the bound parameters.
187    /// 
188    /// # Examples
189    /// 
190    /// For function with parameters:
191    /// ```rust
192    /// [&parameters, p1.clone(), p2.clone()]
193    /// ```
194    fn build_function_arguments(
195        &self,
196        tusk: &Tusk,
197        pattern_bindings: &[(syn::Ident, syn::Ident)],
198        is_default_case: bool,
199        is_external_subcommand_case: bool
200    ) -> Vec<TokenStream> {
201        let has_params_arg = self.tusk_has_parameters_arg(tusk);
202        let mut func_args = Vec::new();
203
204        let mut number_of_non_params_args = tusk.func.sig.inputs.len();
205        if has_params_arg {
206            func_args.push(quote! { &parameters });
207            number_of_non_params_args -= 1;
208        }
209
210        if is_default_case {
211            if is_external_subcommand_case {
212                func_args.push(quote! { Vec::new() });
213            }
214            return func_args;
215        }
216
217        if is_external_subcommand_case && number_of_non_params_args > 0 {
218            func_args.push(quote! { external_subcommand_args.clone() });
219            return func_args;
220        }
221
222        for (_, binding_name) in pattern_bindings {
223            func_args.push(quote! { #binding_name.clone() });
224        }
225
226        func_args
227    }
228
229    /// Creates the full path to the function.
230    /// Handles both local and nested paths.
231    /// 
232    /// # Examples
233    /// 
234    /// - Local path: `super::my_function`
235    /// - Nested path: `super::module1::module2::my_function`
236    fn build_function_path(&self, tusk: &Tusk, path: &[&str]) -> TokenStream {
237        let func_name = &tusk.func.sig.ident;
238
239        if path.is_empty() {
240            quote! { super::#func_name }
241        } else {
242            let path_idents: Vec<_> = path.iter()
243                .map(|p| syn::Ident::new(p, Span::call_site()))
244                .collect();
245            quote! { super::#(#path_idents)::*::#func_name }
246        }
247    }
248}