dampen_core/codegen/
handlers.rs

1//! Handler dispatch code generation
2//!
3//! This module generates efficient handler dispatch code for production builds,
4//! converting handler registry lookups into direct function calls.
5
6use crate::CodegenError;
7use crate::handler::HandlerSignature;
8use proc_macro2::TokenStream;
9use quote::{format_ident, quote};
10
11/// Generate handler dispatch code for production mode
12///
13/// Converts dynamic handler registry lookups into static function calls
14/// for zero runtime overhead.
15///
16/// # Arguments
17/// * `handlers` - List of handler signatures
18/// * `model_name` - Name of the model struct
19/// * `message_name` - Name of the message enum
20///
21/// # Returns
22/// Generated match statement as TokenStream
23pub fn generate_handler_dispatch(
24    handlers: &[HandlerSignature],
25    model_name: &str,
26    message_name: &str,
27) -> Result<TokenStream, CodegenError> {
28    if handlers.is_empty() {
29        return Ok(quote! {
30            match _handler {
31                _ => {}
32            }
33        });
34    }
35
36    let model_ident = format_ident!("{}", model_name);
37    let message_ident = format_ident!("{}", message_name);
38
39    let match_arms: Vec<TokenStream> = handlers
40        .iter()
41        .map(move |handler| {
42            let _handler_ident = format_ident!("{}", handler.name);
43
44            match &handler.param_type {
45                Some(param_type) if handler.returns_command => generate_handler_with_command(
46                    &handler.name,
47                    param_type,
48                    &model_ident,
49                    &message_ident,
50                ),
51                Some(param_type) => generate_handler_with_value(
52                    &handler.name,
53                    param_type,
54                    &model_ident,
55                    &message_ident,
56                ),
57                None if handler.returns_command => {
58                    generate_handler_simple(&handler.name, &model_ident, &message_ident, true)
59                }
60                None => generate_handler_simple(&handler.name, &model_ident, &message_ident, false),
61            }
62        })
63        .collect();
64
65    Ok(quote! {
66        match handler_name {
67            #(#match_arms),*
68        }
69    })
70}
71
72/// Helper function to convert snake_case to UpperCamelCase
73fn to_upper_camel_case(s: &str) -> String {
74    let mut result = String::new();
75    let mut capitalize_next = true;
76    for c in s.chars() {
77        if c == '_' {
78            capitalize_next = true;
79        } else if capitalize_next {
80            result.push(c.to_ascii_uppercase());
81            capitalize_next = false;
82        } else {
83            result.push(c);
84        }
85    }
86    result
87}
88
89/// Generate handler dispatch for simple handlers (no parameters, no return)
90fn generate_handler_simple(
91    handler_name: &str,
92    _model_ident: &syn::Ident,
93    message_ident: &syn::Ident,
94    _returns_command: bool,
95) -> TokenStream {
96    let variant_name = to_upper_camel_case(handler_name);
97    let handler_ident = format_ident!("{}", variant_name);
98    quote! {
99        #handler_name => {
100            #message_ident::#handler_ident
101        }
102    }
103}
104
105/// Generate handler dispatch for handlers that take a value parameter
106///
107/// # Arguments
108/// * `handler_name` - Name of the handler
109/// * `value_type` - Type of the value parameter
110/// * `_model_ident` - Model identifier (unused, kept for API consistency)
111/// * `message_ident` - Message identifier
112///
113/// # Returns
114/// Generated match arm with value parameter
115fn generate_handler_with_value(
116    handler_name: &str,
117    value_type: &str,
118    _model_ident: &syn::Ident,
119    message_ident: &syn::Ident,
120) -> TokenStream {
121    let variant_name = to_upper_camel_case(handler_name);
122    let handler_ident = format_ident!("{}", variant_name);
123    let type_ident = format_ident!("{}", value_type);
124    quote! {
125        #handler_name => {
126            |value: #type_ident| #message_ident::#handler_ident(value)
127        }
128    }
129}
130
131/// Generate handler dispatch for handlers that return commands
132///
133/// # Arguments
134/// * `handler_name` - Name of the handler
135/// * `value_type` - Type of the value parameter (if any)
136/// * `_model_ident` - Model identifier (unused, kept for API consistency)
137/// * `message_ident` - Message identifier
138///
139/// # Returns
140/// Generated match arm with command return
141fn generate_handler_with_command(
142    handler_name: &str,
143    value_type: &str,
144    _model_ident: &syn::Ident,
145    message_ident: &syn::Ident,
146) -> TokenStream {
147    let variant_name = to_upper_camel_case(handler_name);
148    let handler_ident = format_ident!("{}", variant_name);
149    let type_ident = format_ident!("{}", value_type);
150    quote! {
151        #handler_name => {
152            |value: #type_ident| #message_ident::#handler_ident(value)
153        }
154    }
155}
156
157/// Generate the update function body
158///
159/// # Arguments
160/// * `handlers` - List of handler signatures
161/// * `model_name` - Name of the model struct
162/// * `message_name` - Name of the message enum
163///
164/// # Returns
165/// Generated update function as TokenStream
166pub fn generate_update_function(
167    handlers: &[HandlerSignature],
168    model_name: &str,
169    message_name: &str,
170) -> Result<TokenStream, CodegenError> {
171    let message_ident = format_ident!("{}", message_name);
172
173    if handlers.is_empty() {
174        return Ok(quote! {
175            fn update(&mut self, _message: Self::Message) {}
176        });
177    }
178
179    let arms: Vec<TokenStream> = handlers
180        .iter()
181        .map(|handler| {
182            let _model_ident = format_ident!("{}", model_name);
183            let variant_name = to_upper_camel_case(&handler.name);
184            let _handler_ident = format_ident!("{}", variant_name);
185            let handler_name_str = handler.name.clone();
186
187            match (&handler.param_type, handler.returns_command) {
188                (None, false) => {
189                    quote! {
190                        #message_ident::#_handler_ident => {
191                            self.handler_registry.call_simple(&mut self.model, #handler_name_str);
192                        }
193                    }
194                }
195                (Some(param_type), false) => {
196                    let type_ident = format_ident!("{}", param_type);
197                    quote! {
198                        #message_ident::#_handler_ident(value) => {
199                            self.handler_registry.call_with_value::<#type_ident>(&mut self.model, #handler_name_str, value);
200                        }
201                    }
202                }
203                (None, true) => {
204                    quote! {
205                        #message_ident::#_handler_ident => {
206                            if let Some(cmd) = self.handler_registry.call_with_command(&mut self.model, #handler_name_str) {
207                                return cmd;
208                            }
209                        }
210                    }
211                }
212                (Some(param_type), true) => {
213                    let type_ident = format_ident!("{}", param_type);
214                    quote! {
215                        #message_ident::#_handler_ident(value) => {
216                            if let Some(cmd) = self.handler_registry.call_with_command::<#type_ident>(&mut self.model, #handler_name_str, value) {
217                                return cmd;
218                            }
219                        }
220                    }
221                }
222            }
223        })
224        .collect();
225
226    let catch_all = quote! {
227        _ => {}
228    };
229
230    Ok(quote! {
231        fn update(&mut self, message: Self::Message) -> Option<iced::Command<Self::Message>> {
232            match message {
233                #(#arms)*
234                #catch_all
235            }
236            None
237        }
238    })
239}
240
241/// Validate that expressions can be inlined
242///
243/// Returns Err if the expression contains unsupported constructs
244pub fn validate_expression_inlinable(expr: &crate::Expr) -> Result<(), CodegenError> {
245    match expr {
246        crate::Expr::FieldAccess(_) => Ok(()),
247        crate::Expr::SharedFieldAccess(_) => Ok(()), // Shared field access is inlinable
248        crate::Expr::MethodCall(method_expr) => {
249            validate_expression_inlinable(&method_expr.receiver)?;
250            for arg in &method_expr.args {
251                validate_expression_inlinable(arg)?;
252            }
253            Ok(())
254        }
255        crate::Expr::BinaryOp(binary_expr) => {
256            validate_expression_inlinable(&binary_expr.left)?;
257            validate_expression_inlinable(&binary_expr.right)?;
258            Ok(())
259        }
260        crate::Expr::UnaryOp(unary_expr) => {
261            validate_expression_inlinable(&unary_expr.operand)?;
262            Ok(())
263        }
264        crate::Expr::Conditional(cond_expr) => {
265            validate_expression_inlinable(&cond_expr.condition)?;
266            validate_expression_inlinable(&cond_expr.then_branch)?;
267            validate_expression_inlinable(&cond_expr.else_branch)?;
268            Ok(())
269        }
270        crate::Expr::Literal(_) => Ok(()),
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn test_handler_dispatch_simple() {
280        let handlers = vec![HandlerSignature {
281            name: "increment".to_string(),
282            param_type: None,
283            returns_command: false,
284        }];
285
286        let result = generate_handler_dispatch(&handlers, "Model", "Message").unwrap();
287        let code = result.to_string();
288        assert!(code.contains("increment"));
289    }
290
291    #[test]
292    fn test_handler_dispatch_with_value() {
293        let handlers = vec![HandlerSignature {
294            name: "set_value".to_string(),
295            param_type: Some("String".to_string()),
296            returns_command: false,
297        }];
298
299        let result = generate_handler_dispatch(&handlers, "Model", "Message").unwrap();
300        let code = result.to_string();
301        assert!(code.contains("set_value"));
302        assert!(code.contains("String"));
303    }
304
305    #[test]
306    fn test_handler_dispatch_with_command() {
307        let handlers = vec![HandlerSignature {
308            name: "save".to_string(),
309            param_type: None,
310            returns_command: true,
311        }];
312
313        let result = generate_handler_dispatch(&handlers, "Model", "Message").unwrap();
314        let code = result.to_string();
315        assert!(code.contains("save"));
316    }
317
318    #[test]
319    fn test_update_function_generation() {
320        let handlers = vec![
321            HandlerSignature {
322                name: "increment".to_string(),
323                param_type: None,
324                returns_command: false,
325            },
326            HandlerSignature {
327                name: "set_value".to_string(),
328                param_type: Some("String".to_string()),
329                returns_command: false,
330            },
331        ];
332
333        let result = generate_update_function(&handlers, "Model", "Message").unwrap();
334        let code = result.to_string();
335        assert!(code.contains("update"));
336        assert!(code.contains("increment"));
337        assert!(code.contains("set_value"));
338    }
339
340    #[test]
341    fn test_expression_validation() {
342        use crate::expr::ast::{BinaryOp, BinaryOpExpr, Expr, FieldAccessExpr, LiteralExpr};
343
344        let expr = Expr::BinaryOp(BinaryOpExpr {
345            left: Box::new(Expr::FieldAccess(FieldAccessExpr {
346                path: vec!["count".to_string()],
347            })),
348            op: BinaryOp::Add,
349            right: Box::new(Expr::Literal(LiteralExpr::Integer(1))),
350        });
351
352        assert!(validate_expression_inlinable(&expr).is_ok());
353    }
354}