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/// Generate handler dispatch for simple handlers (no parameters, no return)
73fn generate_handler_simple(
74    handler_name: &str,
75    _model_ident: &syn::Ident,
76    message_ident: &syn::Ident,
77    _returns_command: bool,
78) -> TokenStream {
79    let handler_ident = format_ident!("{}", handler_name);
80    quote! {
81        #handler_name => {
82            #message_ident::#handler_ident
83        }
84    }
85}
86
87/// Generate handler dispatch for handlers that take a value parameter
88///
89/// # Arguments
90/// * `handler_name` - Name of the handler
91/// * `value_type` - Type of the value parameter
92/// * `_model_ident` - Model identifier (unused, kept for API consistency)
93/// * `message_ident` - Message identifier
94///
95/// # Returns
96/// Generated match arm with value parameter
97fn generate_handler_with_value(
98    handler_name: &str,
99    value_type: &str,
100    _model_ident: &syn::Ident,
101    message_ident: &syn::Ident,
102) -> TokenStream {
103    let handler_ident = format_ident!("{}", handler_name);
104    let type_ident = format_ident!("{}", value_type);
105    quote! {
106        #handler_name => {
107            |value: #type_ident| #message_ident::#handler_ident(value)
108        }
109    }
110}
111
112/// Generate handler dispatch for handlers that return commands
113///
114/// # Arguments
115/// * `handler_name` - Name of the handler
116/// * `value_type` - Type of the value parameter (if any)
117/// * `_model_ident` - Model identifier (unused, kept for API consistency)
118/// * `message_ident` - Message identifier
119///
120/// # Returns
121/// Generated match arm with command return
122fn generate_handler_with_command(
123    handler_name: &str,
124    value_type: &str,
125    _model_ident: &syn::Ident,
126    message_ident: &syn::Ident,
127) -> TokenStream {
128    let handler_ident = format_ident!("{}", handler_name);
129    let type_ident = format_ident!("{}", value_type);
130    quote! {
131        #handler_name => {
132            |value: #type_ident| #message_ident::#handler_ident(value)
133        }
134    }
135}
136
137/// Generate the update function body
138///
139/// # Arguments
140/// * `handlers` - List of handler signatures
141/// * `model_name` - Name of the model struct
142/// * `message_name` - Name of the message enum
143///
144/// # Returns
145/// Generated update function as TokenStream
146pub fn generate_update_function(
147    handlers: &[HandlerSignature],
148    model_name: &str,
149    message_name: &str,
150) -> Result<TokenStream, CodegenError> {
151    let message_ident = format_ident!("{}", message_name);
152
153    if handlers.is_empty() {
154        return Ok(quote! {
155            fn update(&mut self, _message: Self::Message) {}
156        });
157    }
158
159    let arms: Vec<TokenStream> = handlers
160        .iter()
161        .map(|handler| {
162            let _model_ident = format_ident!("{}", model_name);
163            let _handler_ident = format_ident!("{}", handler.name);
164            let handler_name_str = handler.name.clone();
165
166            match (&handler.param_type, handler.returns_command) {
167                (None, false) => {
168                    quote! {
169                        #message_ident::#_handler_ident => {
170                            self.handler_registry.call_simple(&mut self.model, #handler_name_str);
171                        }
172                    }
173                }
174                (Some(param_type), false) => {
175                    let type_ident = format_ident!("{}", param_type);
176                    quote! {
177                        #message_ident::#_handler_ident(value) => {
178                            self.handler_registry.call_with_value::<#type_ident>(&mut self.model, #handler_name_str, value);
179                        }
180                    }
181                }
182                (None, true) => {
183                    quote! {
184                        #message_ident::#_handler_ident => {
185                            if let Some(cmd) = self.handler_registry.call_with_command(&mut self.model, #handler_name_str) {
186                                return cmd;
187                            }
188                        }
189                    }
190                }
191                (Some(param_type), true) => {
192                    let type_ident = format_ident!("{}", param_type);
193                    quote! {
194                        #message_ident::#_handler_ident(value) => {
195                            if let Some(cmd) = self.handler_registry.call_with_command::<#type_ident>(&mut self.model, #handler_name_str, value) {
196                                return cmd;
197                            }
198                        }
199                    }
200                }
201            }
202        })
203        .collect();
204
205    let catch_all = quote! {
206        _ => {}
207    };
208
209    Ok(quote! {
210        fn update(&mut self, message: Self::Message) -> Option<iced::Command<Self::Message>> {
211            match message {
212                #(#arms)*
213                #catch_all
214            }
215            None
216        }
217    })
218}
219
220/// Validate that expressions can be inlined
221///
222/// Returns Err if the expression contains unsupported constructs
223pub fn validate_expression_inlinable(expr: &crate::Expr) -> Result<(), CodegenError> {
224    match expr {
225        crate::Expr::FieldAccess(_) => Ok(()),
226        crate::Expr::MethodCall(method_expr) => {
227            validate_expression_inlinable(&method_expr.receiver)?;
228            for arg in &method_expr.args {
229                validate_expression_inlinable(arg)?;
230            }
231            Ok(())
232        }
233        crate::Expr::BinaryOp(binary_expr) => {
234            validate_expression_inlinable(&binary_expr.left)?;
235            validate_expression_inlinable(&binary_expr.right)?;
236            Ok(())
237        }
238        crate::Expr::UnaryOp(unary_expr) => {
239            validate_expression_inlinable(&unary_expr.operand)?;
240            Ok(())
241        }
242        crate::Expr::Conditional(cond_expr) => {
243            validate_expression_inlinable(&cond_expr.condition)?;
244            validate_expression_inlinable(&cond_expr.then_branch)?;
245            validate_expression_inlinable(&cond_expr.else_branch)?;
246            Ok(())
247        }
248        crate::Expr::Literal(_) => Ok(()),
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn test_handler_dispatch_simple() {
258        let handlers = vec![HandlerSignature {
259            name: "increment".to_string(),
260            param_type: None,
261            returns_command: false,
262        }];
263
264        let result = generate_handler_dispatch(&handlers, "Model", "Message").unwrap();
265        let code = result.to_string();
266        assert!(code.contains("increment"));
267    }
268
269    #[test]
270    fn test_handler_dispatch_with_value() {
271        let handlers = vec![HandlerSignature {
272            name: "set_value".to_string(),
273            param_type: Some("String".to_string()),
274            returns_command: false,
275        }];
276
277        let result = generate_handler_dispatch(&handlers, "Model", "Message").unwrap();
278        let code = result.to_string();
279        assert!(code.contains("set_value"));
280        assert!(code.contains("String"));
281    }
282
283    #[test]
284    fn test_handler_dispatch_with_command() {
285        let handlers = vec![HandlerSignature {
286            name: "save".to_string(),
287            param_type: None,
288            returns_command: true,
289        }];
290
291        let result = generate_handler_dispatch(&handlers, "Model", "Message").unwrap();
292        let code = result.to_string();
293        assert!(code.contains("save"));
294    }
295
296    #[test]
297    fn test_update_function_generation() {
298        let handlers = vec![
299            HandlerSignature {
300                name: "increment".to_string(),
301                param_type: None,
302                returns_command: false,
303            },
304            HandlerSignature {
305                name: "set_value".to_string(),
306                param_type: Some("String".to_string()),
307                returns_command: false,
308            },
309        ];
310
311        let result = generate_update_function(&handlers, "Model", "Message").unwrap();
312        let code = result.to_string();
313        assert!(code.contains("update"));
314        assert!(code.contains("increment"));
315        assert!(code.contains("set_value"));
316    }
317
318    #[test]
319    fn test_expression_validation() {
320        use crate::expr::ast::{BinaryOp, BinaryOpExpr, Expr, FieldAccessExpr, LiteralExpr};
321
322        let expr = Expr::BinaryOp(BinaryOpExpr {
323            left: Box::new(Expr::FieldAccess(FieldAccessExpr {
324                path: vec!["count".to_string()],
325            })),
326            op: BinaryOp::Add,
327            right: Box::new(Expr::Literal(LiteralExpr::Integer(1))),
328        });
329
330        assert!(validate_expression_inlinable(&expr).is_ok());
331    }
332}