Coverage Report

Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/backend/transpiler/expressions.rs
Line
Count
Source
1
//! Expression transpilation methods
2
3
#![allow(clippy::missing_errors_doc)]
4
#![allow(clippy::needless_pass_by_value)] // TokenStream by value is intentional for quote! macro
5
6
use super::Transpiler;
7
use crate::frontend::ast::{BinaryOp::{self, NullCoalesce}, Expr, ExprKind, Literal, StringPart, UnaryOp};
8
use anyhow::{bail, Result};
9
use proc_macro2::TokenStream;
10
use quote::{format_ident, quote};
11
12
impl Transpiler {
13
    /// Transpiles literal values
14
0
    pub fn transpile_literal(lit: &Literal) -> TokenStream {
15
0
        match lit {
16
0
            Literal::Integer(i) => Self::transpile_integer(*i),
17
0
            Literal::Float(f) => quote! { #f },
18
0
            Literal::Unit => quote! { () },
19
0
            _ => Self::transpile_simple_literal(lit),
20
        }
21
0
    }
22
23
0
    fn transpile_simple_literal(lit: &Literal) -> TokenStream {
24
0
        match lit {
25
0
            Literal::String(s) => quote! { #s },
26
0
            Literal::Bool(b) => quote! { #b },
27
0
            Literal::Char(c) => quote! { #c },
28
0
            _ => unreachable!(),
29
        }
30
0
    }
31
32
0
    fn transpile_integer(i: i64) -> TokenStream {
33
        // Integer literals in Rust need proper type handling
34
        // Use i32 for values that fit, i64 otherwise
35
0
        if let Ok(i32_val) = i32::try_from(i) {
36
            // Use i32 suffix for clarity and to match struct field types
37
0
            let literal = proc_macro2::Literal::i32_suffixed(i32_val);
38
0
            quote! { #literal }
39
        } else {
40
            // For large integers, we need i64 suffix
41
0
            let literal = proc_macro2::Literal::i64_suffixed(i);
42
0
            quote! { #literal }
43
        }
44
0
    }
45
46
    /// Transpiles string interpolation
47
    ///
48
    /// # Errors
49
    /// Returns an error if expression transpilation fails
50
0
    pub fn transpile_string_interpolation(&self, parts: &[StringPart]) -> Result<TokenStream> {
51
0
        if parts.is_empty() {
52
0
            return Ok(quote! { "" });
53
0
        }
54
55
0
        let mut format_string = String::new();
56
0
        let mut args = Vec::new();
57
58
0
        for part in parts {
59
0
            match part {
60
0
                StringPart::Text(s) => {
61
0
                    // Escape any format specifiers in literal parts
62
0
                    format_string.push_str(&s.replace('{', "{{").replace('}', "}}"));
63
0
                }
64
0
                StringPart::Expr(expr) => {
65
0
                    format_string.push_str("{}");
66
0
                    let expr_tokens = self.transpile_expr(expr)?;
67
0
                    args.push(expr_tokens);
68
                }
69
0
                StringPart::ExprWithFormat { expr, format_spec } => {
70
                    // Include the format specifier in the format string
71
0
                    format_string.push('{');
72
0
                    format_string.push_str(format_spec);
73
0
                    format_string.push('}');
74
0
                    let expr_tokens = self.transpile_expr(expr)?;
75
0
                    args.push(expr_tokens);
76
                }
77
            }
78
        }
79
80
0
        Ok(quote! {
81
            format!(#format_string #(, #args)*)
82
        })
83
0
    }
84
85
    /// Transpiles binary operations
86
0
    pub fn transpile_binary(&self, left: &Expr, op: BinaryOp, right: &Expr) -> Result<TokenStream> {
87
        // Special handling for string concatenation
88
        // Only treat as string concatenation if at least one operand is definitely a string
89
0
        if op == BinaryOp::Add && (Self::is_definitely_string(left) || Self::is_definitely_string(right)) {
90
0
            return self.transpile_string_concatenation(left, right);
91
0
        }
92
93
        // Transpile operands with precedence-aware parentheses
94
0
        let left_tokens = self.transpile_expr_with_precedence(left, op, true)?;
95
0
        let right_tokens = self.transpile_expr_with_precedence(right, op, false)?;
96
97
0
        Ok(Self::transpile_binary_op(left_tokens, op, right_tokens))
98
0
    }
99
100
    /// Transpile expression with precedence-aware parentheses
101
    /// 
102
    /// Adds parentheses around sub-expressions when needed to preserve precedence
103
0
    fn transpile_expr_with_precedence(&self, expr: &Expr, parent_op: BinaryOp, is_left_operand: bool) -> Result<TokenStream> {
104
0
        let tokens = self.transpile_expr(expr)?;
105
        
106
        // Check if we need parentheses
107
0
        if let ExprKind::Binary { op: child_op, .. } = &expr.kind {
108
0
            let parent_prec = Self::get_operator_precedence(parent_op);
109
0
            let child_prec = Self::get_operator_precedence(*child_op);
110
            
111
            // Add parentheses if child has lower precedence
112
            // For right operands, also add parentheses if precedence is equal and parent is right-associative  
113
0
            let needs_parens = child_prec < parent_prec ||
114
0
                (!is_left_operand && child_prec == parent_prec && Self::is_right_associative(parent_op));
115
            
116
0
            if needs_parens {
117
0
                return Ok(quote! { (#tokens) });
118
0
            }
119
0
        }
120
        
121
0
        Ok(tokens)
122
0
    }
123
124
    /// Get operator precedence (higher number = higher precedence)
125
0
    fn get_operator_precedence(op: BinaryOp) -> i32 {
126
0
        match op {
127
0
            BinaryOp::Or => 10,
128
0
            BinaryOp::And => 20,
129
0
            BinaryOp::Equal | BinaryOp::NotEqual => 30,
130
0
            BinaryOp::Less | BinaryOp::LessEqual | BinaryOp::Greater | BinaryOp::GreaterEqual => 40,
131
0
            BinaryOp::Add | BinaryOp::Subtract => 50,
132
0
            BinaryOp::Multiply | BinaryOp::Divide | BinaryOp::Modulo => 60,
133
0
            BinaryOp::Power => 70,
134
0
            _ => 0, // Default for other operators
135
        }
136
0
    }
137
    
138
    /// Check if operator is right-associative
139
0
    fn is_right_associative(op: BinaryOp) -> bool {
140
0
        matches!(op, BinaryOp::Power) // Only power is right-associative in most languages
141
0
    }
142
143
0
    fn transpile_binary_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
144
        use BinaryOp::{
145
            Add, And, BitwiseAnd, BitwiseOr, BitwiseXor, Divide, Equal, Greater, GreaterEqual,
146
            LeftShift, Less, LessEqual, Modulo, Multiply, NotEqual, Or, Power,
147
            Subtract,
148
        };
149
0
        match op {
150
            // Arithmetic operations
151
            Add | Subtract | Multiply | Divide | Modulo | Power => {
152
0
                Self::transpile_arithmetic_op(left, op, right)
153
            }
154
            // Comparison operations
155
            Equal | NotEqual | Less | LessEqual | Greater | GreaterEqual => {
156
0
                Self::transpile_comparison_op(left, op, right)
157
            }
158
            // Logical operations
159
0
            And | Or | NullCoalesce => Self::transpile_logical_op(left, op, right),
160
            // Bitwise operations
161
            BitwiseAnd | BitwiseOr | BitwiseXor | LeftShift => {
162
0
                Self::transpile_bitwise_op(left, op, right)
163
            }
164
        }
165
0
    }
166
167
0
    fn transpile_arithmetic_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
168
        use BinaryOp::{Add, Divide, Modulo, Multiply, Power, Subtract};
169
0
        match op {
170
            Add | Subtract | Multiply | Divide | Modulo => {
171
0
                Self::transpile_basic_arithmetic(left, op, right)
172
            }
173
0
            Power => quote! { #left.pow(#right) },
174
0
            _ => unreachable!(),
175
        }
176
0
    }
177
178
0
    fn transpile_basic_arithmetic(
179
0
        left: TokenStream,
180
0
        op: BinaryOp,
181
0
        right: TokenStream,
182
0
    ) -> TokenStream {
183
        // Reduce complexity by splitting into smaller functions
184
0
        match op {
185
0
            BinaryOp::Add => quote! { #left + #right },
186
0
            BinaryOp::Subtract => quote! { #left - #right },
187
0
            BinaryOp::Multiply => quote! { #left * #right },
188
0
            _ => Self::transpile_division_mod(left, op, right),
189
        }
190
0
    }
191
192
0
    fn transpile_division_mod(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
193
0
        match op {
194
0
            BinaryOp::Divide => quote! { #left / #right },
195
0
            BinaryOp::Modulo => quote! { #left % #right },
196
0
            _ => unreachable!(),
197
        }
198
0
    }
199
200
0
    fn transpile_comparison_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
201
        use BinaryOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual};
202
0
        match op {
203
0
            Equal | NotEqual => Self::transpile_equality(left, op, right),
204
0
            Less | LessEqual | Greater | GreaterEqual => Self::transpile_ordering(left, op, right),
205
0
            _ => unreachable!(),
206
        }
207
0
    }
208
209
0
    fn transpile_equality(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
210
0
        match op {
211
0
            BinaryOp::Equal => quote! { #left == #right },
212
0
            BinaryOp::NotEqual => quote! { #left != #right },
213
0
            _ => unreachable!(),
214
        }
215
0
    }
216
217
0
    fn transpile_ordering(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
218
0
        match op {
219
0
            BinaryOp::Less => quote! { #left < #right },
220
0
            BinaryOp::LessEqual => quote! { #left <= #right },
221
0
            _ => Self::transpile_greater_ops(left, op, right),
222
        }
223
0
    }
224
225
0
    fn transpile_greater_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
226
0
        match op {
227
0
            BinaryOp::Greater => quote! { #left > #right },
228
0
            BinaryOp::GreaterEqual => quote! { #left >= #right },
229
0
            _ => unreachable!(),
230
        }
231
0
    }
232
233
0
    fn transpile_logical_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
234
0
        match op {
235
0
            BinaryOp::And => quote! { #left && #right },
236
0
            BinaryOp::Or => quote! { #left || #right },
237
0
            _ => unreachable!(),
238
        }
239
0
    }
240
241
0
    fn transpile_bitwise_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
242
        use BinaryOp::{BitwiseAnd, BitwiseOr, BitwiseXor};
243
0
        match op {
244
0
            BitwiseAnd => quote! { #left & #right },
245
0
            BitwiseOr => quote! { #left | #right },
246
0
            BitwiseXor => quote! { #left ^ #right },
247
0
            _ => Self::transpile_shift_ops(left, op, right),
248
        }
249
0
    }
250
251
0
    fn transpile_shift_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
252
0
        match op {
253
0
            BinaryOp::LeftShift => quote! { #left << #right },
254
0
            _ => unreachable!(),
255
        }
256
0
    }
257
258
    /// Transpiles unary operations  
259
0
    pub fn transpile_unary(&self, op: UnaryOp, operand: &Expr) -> Result<TokenStream> {
260
0
        let operand_tokens = self.transpile_expr(operand)?;
261
262
0
        Ok(match op {
263
0
            UnaryOp::Not | UnaryOp::BitwiseNot => quote! { !#operand_tokens },
264
0
            UnaryOp::Negate => quote! { -#operand_tokens },
265
0
            UnaryOp::Reference => quote! { &#operand_tokens },
266
        })
267
0
    }
268
269
270
    /// Transpiles await expressions
271
0
    pub fn transpile_await(&self, expr: &Expr) -> Result<TokenStream> {
272
0
        let expr_tokens = self.transpile_expr(expr)?;
273
0
        Ok(quote! { #expr_tokens.await })
274
0
    }
275
276
    /// Transpiles async blocks
277
0
    pub fn transpile_async_block(&self, body: &Expr) -> Result<TokenStream> {
278
0
        let body_tokens = self.transpile_expr(body)?;
279
0
        Ok(quote! { async { #body_tokens } })
280
0
    }
281
282
    /// Transpiles throw expressions (panic in Rust)
283
0
    pub fn transpile_throw(&self, expr: &Expr) -> Result<TokenStream> {
284
0
        let expr_tokens = self.transpile_expr(expr)?;
285
0
        Ok(quote! {
286
0
            panic!(#expr_tokens)
287
0
        })
288
0
    }
289
290
    /// Transpiles field access
291
0
    pub fn transpile_field_access(&self, object: &Expr, field: &str) -> Result<TokenStream> {
292
        use crate::frontend::ast::ExprKind;
293
        
294
0
        let obj_tokens = self.transpile_expr(object)?;
295
        
296
        // Check if the object is an ObjectLiteral (HashMap) or module path
297
0
        match &object.kind {
298
            ExprKind::ObjectLiteral { .. } => {
299
                // Direct object literal access - use get()
300
0
                Ok(quote! { 
301
0
                    #obj_tokens.get(#field)
302
0
                        .cloned()
303
0
                        .unwrap_or_else(|| panic!("Field '{}' not found", #field))
304
0
                })
305
            }
306
            ExprKind::FieldAccess { .. } => {
307
                // Nested field access like net::TcpListener - use :: syntax
308
0
                let field_ident = format_ident!("{}", field);
309
0
                Ok(quote! { #obj_tokens::#field_ident })
310
            }
311
0
            ExprKind::Identifier(name) if name.contains("::") => {
312
                // Module path identifier - use :: syntax
313
0
                let field_ident = format_ident!("{}", field);
314
0
                Ok(quote! { #obj_tokens::#field_ident })
315
            }
316
            _ => {
317
                // For other cases, assume HashMap access
318
0
                Ok(quote! { 
319
0
                    #obj_tokens.get(#field)
320
0
                        .cloned()
321
0
                        .unwrap_or_else(|| panic!("Field '{}' not found", #field))
322
0
                })
323
            }
324
        }
325
0
    }
326
327
    /// Transpiles index access `(array[index])`
328
0
    pub fn transpile_index_access(&self, object: &Expr, index: &Expr) -> Result<TokenStream> {
329
        use crate::frontend::ast::{ExprKind, Literal};
330
        
331
0
        let obj_tokens = self.transpile_expr(object)?;
332
0
        let index_tokens = self.transpile_expr(index)?;
333
        
334
        // Smart index access: HashMap.get() for string keys, array indexing for numeric
335
0
        match &index.kind {
336
            // String literal keys use HashMap.get()
337
            ExprKind::Literal(Literal::String(_)) => {
338
0
                Ok(quote! { 
339
0
                    #obj_tokens.get(#index_tokens)
340
0
                        .cloned()
341
0
                        .unwrap_or_else(|| panic!("Key not found"))
342
0
                })
343
            }
344
            // Numeric and other keys use array indexing
345
            _ => {
346
0
                Ok(quote! { #obj_tokens[#index_tokens as usize] })
347
            }
348
        }
349
0
    }
350
351
    /// Transpiles slice access `(array[start:end])`
352
0
    pub fn transpile_slice(&self, object: &Expr, start: Option<&Expr>, end: Option<&Expr>) -> Result<TokenStream> {
353
0
        let obj_tokens = self.transpile_expr(object)?;
354
        
355
0
        match (start, end) {
356
            (None, None) => {
357
                // Full slice [..]
358
0
                Ok(quote! { &#obj_tokens[..] })
359
            }
360
0
            (None, Some(end)) => {
361
                // Slice from beginning [..end]
362
0
                let end_tokens = self.transpile_expr(end)?;
363
0
                Ok(quote! { &#obj_tokens[..#end_tokens as usize] })
364
            }
365
0
            (Some(start), None) => {
366
                // Slice to end [start..]
367
0
                let start_tokens = self.transpile_expr(start)?;
368
0
                Ok(quote! { &#obj_tokens[#start_tokens as usize..] })
369
            }
370
0
            (Some(start), Some(end)) => {
371
                // Full range slice [start..end]
372
0
                let start_tokens = self.transpile_expr(start)?;
373
0
                let end_tokens = self.transpile_expr(end)?;
374
0
                Ok(quote! { &#obj_tokens[#start_tokens as usize..#end_tokens as usize] })
375
            }
376
        }
377
0
    }
378
379
    /// Transpiles assignment
380
0
    pub fn transpile_assign(&self, target: &Expr, value: &Expr) -> Result<TokenStream> {
381
0
        let target_tokens = self.transpile_expr(target)?;
382
0
        let value_tokens = self.transpile_expr(value)?;
383
0
        Ok(quote! { #target_tokens = #value_tokens })
384
0
    }
385
386
    /// Transpiles compound assignment
387
0
    pub fn transpile_compound_assign(
388
0
        &self,
389
0
        target: &Expr,
390
0
        op: BinaryOp,
391
0
        value: &Expr,
392
0
    ) -> Result<TokenStream> {
393
0
        let target_tokens = self.transpile_expr(target)?;
394
0
        let value_tokens = self.transpile_expr(value)?;
395
0
        let op_tokens = Self::get_compound_op_token(op)?;
396
397
0
        Ok(quote! { #target_tokens #op_tokens #value_tokens })
398
0
    }
399
400
0
    fn get_compound_op_token(op: BinaryOp) -> Result<TokenStream> {
401
        use BinaryOp::{Add, Divide, Modulo, Multiply, Subtract};
402
0
        match op {
403
0
            Add | Subtract | Multiply => Ok(Self::get_basic_compound_token(op)),
404
0
            Divide | Modulo => Ok(Self::get_division_compound_token(op)),
405
            _ => {
406
                use anyhow::bail;
407
0
                bail!("Invalid operator for compound assignment: {:?}", op)
408
            }
409
        }
410
0
    }
411
412
0
    fn get_basic_compound_token(op: BinaryOp) -> TokenStream {
413
0
        match op {
414
0
            BinaryOp::Add => quote! { += },
415
0
            BinaryOp::Subtract => quote! { -= },
416
0
            BinaryOp::Multiply => quote! { *= },
417
0
            _ => unreachable!(),
418
        }
419
0
    }
420
421
0
    fn get_division_compound_token(op: BinaryOp) -> TokenStream {
422
0
        match op {
423
0
            BinaryOp::Divide => quote! { /= },
424
0
            BinaryOp::Modulo => quote! { %= },
425
0
            _ => unreachable!(),
426
        }
427
0
    }
428
429
    /// Transpiles pre-increment
430
0
    pub fn transpile_pre_increment(&self, target: &Expr) -> Result<TokenStream> {
431
0
        let target_tokens = self.transpile_expr(target)?;
432
0
        Ok(quote! { { #target_tokens += 1; #target_tokens } })
433
0
    }
434
435
    /// Transpiles post-increment
436
0
    pub fn transpile_post_increment(&self, target: &Expr) -> Result<TokenStream> {
437
0
        let target_tokens = self.transpile_expr(target)?;
438
0
        Ok(quote! {
439
0
            {
440
0
                let _tmp = #target_tokens;
441
0
                #target_tokens += 1;
442
0
                _tmp
443
0
            }
444
0
        })
445
0
    }
446
447
    /// Transpiles pre-decrement
448
0
    pub fn transpile_pre_decrement(&self, target: &Expr) -> Result<TokenStream> {
449
0
        let target_tokens = self.transpile_expr(target)?;
450
0
        Ok(quote! { { #target_tokens -= 1; #target_tokens } })
451
0
    }
452
453
    /// Transpiles post-decrement
454
0
    pub fn transpile_post_decrement(&self, target: &Expr) -> Result<TokenStream> {
455
0
        let target_tokens = self.transpile_expr(target)?;
456
0
        Ok(quote! {
457
0
            {
458
0
                let _tmp = #target_tokens;
459
0
                #target_tokens -= 1;
460
0
                _tmp
461
0
            }
462
0
        })
463
0
    }
464
465
    /// Transpiles list literals
466
0
    pub fn transpile_list(&self, elements: &[Expr]) -> Result<TokenStream> {
467
        // Check if any elements are spread expressions
468
0
        let has_spread = elements.iter().any(|e| matches!(e.kind, crate::frontend::ast::ExprKind::Spread { .. }));
469
        
470
0
        if has_spread {
471
            // Handle spread expressions by building vector with extends
472
0
            let mut statements = Vec::new();
473
0
            statements.push(quote! { let mut __temp_vec = Vec::new(); });
474
            
475
0
            for element in elements {
476
0
                if let crate::frontend::ast::ExprKind::Spread { expr } = &element.kind {
477
0
                    let expr_tokens = self.transpile_expr(expr)?;
478
0
                    statements.push(quote! { __temp_vec.extend(#expr_tokens); });
479
                } else {
480
0
                    let expr_tokens = self.transpile_expr(element)?;
481
0
                    statements.push(quote! { __temp_vec.push(#expr_tokens); });
482
                }
483
            }
484
            
485
0
            statements.push(quote! { __temp_vec });
486
0
            Ok(quote! { { #(#statements)* } })
487
        } else {
488
            // No spread expressions, use simple vec![] macro
489
0
            let element_tokens: Result<Vec<_>> =
490
0
                elements.iter().map(|e| self.transpile_expr(e)).collect();
491
0
            let element_tokens = element_tokens?;
492
0
            Ok(quote! { vec![#(#element_tokens),*] })
493
        }
494
0
    }
495
496
    /// Transpiles tuple literals
497
0
    pub fn transpile_tuple(&self, elements: &[Expr]) -> Result<TokenStream> {
498
0
        let element_tokens: Result<Vec<_>> =
499
0
            elements.iter().map(|e| self.transpile_expr(e)).collect();
500
0
        let element_tokens = element_tokens?;
501
0
        Ok(quote! { (#(#element_tokens),*) })
502
0
    }
503
504
    /// Transpiles range expressions
505
0
    pub fn transpile_range(
506
0
        &self,
507
0
        start: &Expr,
508
0
        end: &Expr,
509
0
        inclusive: bool,
510
0
    ) -> Result<TokenStream> {
511
0
        let start_tokens = self.transpile_expr(start)?;
512
0
        let end_tokens = self.transpile_expr(end)?;
513
514
0
        if inclusive {
515
0
            Ok(quote! { #start_tokens..=#end_tokens })
516
        } else {
517
0
            Ok(quote! { #start_tokens..#end_tokens })
518
        }
519
0
    }
520
521
    /// Transpiles object literals
522
0
    pub fn transpile_object_literal(
523
0
        &self,
524
0
        fields: &[crate::frontend::ast::ObjectField],
525
0
    ) -> Result<TokenStream> {
526
0
        let field_tokens = self.collect_hashmap_field_tokens(fields)?;
527
0
        Ok(quote! {
528
            {
529
                let mut map: std::collections::HashMap<String, String> = std::collections::HashMap::new();
530
                #(#field_tokens)*
531
                map
532
            }
533
        })
534
0
    }
535
536
0
    fn collect_hashmap_field_tokens(
537
0
        &self,
538
0
        fields: &[crate::frontend::ast::ObjectField],
539
0
    ) -> Result<Vec<TokenStream>> {
540
        use crate::frontend::ast::ObjectField;
541
0
        let mut field_tokens = Vec::new();
542
543
0
        for field in fields {
544
0
            let token = match field {
545
0
                ObjectField::KeyValue { key, value } => {
546
0
                    let value_tokens = self.transpile_expr(value)?;
547
0
                    quote! { map.insert(#key.to_string(), (#value_tokens).to_string()); }
548
                }
549
0
                ObjectField::Spread { expr } => {
550
0
                    let expr_tokens = self.transpile_expr(expr)?;
551
                    // For spread syntax, merge the other map into this one
552
0
                    quote! { 
553
                        for (k, v) in #expr_tokens {
554
                            map.insert(k, v);
555
                        }
556
                    }
557
                }
558
            };
559
0
            field_tokens.push(token);
560
        }
561
562
0
        Ok(field_tokens)
563
0
    }
564
565
566
    /// Transpiles struct literals
567
0
    pub fn transpile_struct_literal(
568
0
        &self,
569
0
        name: &str,
570
0
        fields: &[(String, Expr)],
571
0
    ) -> Result<TokenStream> {
572
0
        let struct_name = format_ident!("{}", name);
573
0
        let mut field_tokens = Vec::new();
574
575
0
        for (field_name, value) in fields {
576
0
            let field_ident = format_ident!("{}", field_name);
577
0
            let value_tokens = match &value.kind {
578
                // Convert string literals to String for struct fields
579
0
                ExprKind::Literal(Literal::String(s)) => {
580
0
                    quote! { #s.to_string() }
581
                }
582
0
                _ => self.transpile_expr(value)?,
583
            };
584
0
            field_tokens.push(quote! { #field_ident: #value_tokens });
585
        }
586
587
0
        Ok(quote! {
588
            #struct_name {
589
                #(#field_tokens,)*
590
            }
591
        })
592
0
    }
593
594
    /// Check if an expression is definitely a string (conservative detection)
595
0
    fn is_definitely_string(expr: &Expr) -> bool {
596
0
        match &expr.kind {
597
            // String literals are definitely strings
598
0
            ExprKind::Literal(Literal::String(_)) => true,
599
            // String interpolation is definitely strings
600
0
            ExprKind::StringInterpolation { .. } => true,
601
            // Binary expressions with + that involve strings are string concatenations
602
0
            ExprKind::Binary { op: BinaryOp::Add, left, right } => {
603
0
                Self::is_definitely_string(left) || Self::is_definitely_string(right)
604
            },
605
            // Method calls on strings that return strings
606
0
            ExprKind::MethodCall { receiver, method, .. } => {
607
0
                matches!(method.as_str(), "to_string" | "trim" | "to_uppercase" | "to_lowercase") ||
608
0
                Self::is_definitely_string(receiver)
609
            },
610
            // Variables could be strings, but we can't be sure without type info
611
            // For now, be conservative and don't assume variables are strings
612
0
            ExprKind::Identifier(_) => false,
613
            // Function calls are NOT definitely strings - they could return any type
614
0
            ExprKind::Call { .. } => false,
615
            // Other expressions are not strings
616
0
            _ => false,
617
        }
618
0
    }
619
620
621
    /// Transpile string concatenation using proper Rust string operations
622
0
    fn transpile_string_concatenation(&self, left: &Expr, right: &Expr) -> Result<TokenStream> {
623
0
        let left_tokens = self.transpile_expr(left)?;
624
0
        let right_tokens = self.transpile_expr(right)?;
625
        
626
        // Use format! with proper string handling - convert both to strings to avoid type mismatches
627
        // This avoids the String + String issue in Rust by using format! exclusively
628
0
        Ok(quote! { format!("{}{}", #left_tokens, #right_tokens) })
629
0
    }
630
}
631
632
#[cfg(test)]
633
#[allow(clippy::single_char_pattern)]
634
mod tests {
635
    use super::*;
636
    use crate::{Parser, frontend::ast::ExprKind};
637
638
    fn create_transpiler() -> Transpiler {
639
        Transpiler::new()
640
    }
641
642
    #[test]
643
    fn test_transpile_integer_literal() {
644
        let transpiler = create_transpiler();
645
        let code = "42";
646
        let mut parser = Parser::new(code);
647
        let ast = parser.parse().unwrap();
648
        
649
        let result = transpiler.transpile(&ast).unwrap();
650
        let rust_str = result.to_string();
651
        
652
        assert!(rust_str.contains("42"));
653
    }
654
655
    #[test]
656
    fn test_transpile_float_literal() {
657
        let transpiler = create_transpiler();
658
        let code = "3.14";
659
        let mut parser = Parser::new(code);
660
        let ast = parser.parse().unwrap();
661
        
662
        let result = transpiler.transpile(&ast).unwrap();
663
        let rust_str = result.to_string();
664
        
665
        assert!(rust_str.contains("3.14"));
666
    }
667
668
    #[test]
669
    fn test_transpile_string_literal() {
670
        let transpiler = create_transpiler();
671
        let code = "\"hello\"";
672
        let mut parser = Parser::new(code);
673
        let ast = parser.parse().unwrap();
674
        
675
        let result = transpiler.transpile(&ast).unwrap();
676
        let rust_str = result.to_string();
677
        
678
        assert!(rust_str.contains("hello"));
679
    }
680
681
    #[test]
682
    fn test_transpile_boolean_literal() {
683
        let transpiler = create_transpiler();
684
        let code = "true";
685
        let mut parser = Parser::new(code);
686
        let ast = parser.parse().unwrap();
687
        
688
        let result = transpiler.transpile(&ast).unwrap();
689
        let rust_str = result.to_string();
690
        
691
        assert!(rust_str.contains("true"));
692
    }
693
694
    #[test]
695
    fn test_transpile_unit_literal() {
696
        let transpiler = create_transpiler();
697
        let code = "()";
698
        let mut parser = Parser::new(code);
699
        let ast = parser.parse().unwrap();
700
        
701
        let result = transpiler.transpile(&ast).unwrap();
702
        let rust_str = result.to_string();
703
        
704
        assert!(rust_str.contains("()"));
705
    }
706
707
    #[test]
708
    fn test_transpile_binary_addition() {
709
        let transpiler = create_transpiler();
710
        let code = "5 + 3";
711
        let mut parser = Parser::new(code);
712
        let ast = parser.parse().unwrap();
713
        
714
        let result = transpiler.transpile(&ast).unwrap();
715
        let rust_str = result.to_string();
716
        
717
        assert!(rust_str.contains("5") && rust_str.contains("3"));
718
        assert!(rust_str.contains("+"));
719
    }
720
721
    #[test]
722
    fn test_transpile_binary_subtraction() {
723
        let transpiler = create_transpiler();
724
        let code = "10 - 4";
725
        let mut parser = Parser::new(code);
726
        let ast = parser.parse().unwrap();
727
        
728
        let result = transpiler.transpile(&ast).unwrap();
729
        let rust_str = result.to_string();
730
        
731
        assert!(rust_str.contains("10") && rust_str.contains("4"));
732
        assert!(rust_str.contains("-"));
733
    }
734
735
    #[test]
736
    fn test_transpile_binary_multiplication() {
737
        let transpiler = create_transpiler();
738
        let code = "6 * 7";
739
        let mut parser = Parser::new(code);
740
        let ast = parser.parse().unwrap();
741
        
742
        let result = transpiler.transpile(&ast).unwrap();
743
        let rust_str = result.to_string();
744
        
745
        assert!(rust_str.contains("6") && rust_str.contains("7"));
746
        assert!(rust_str.contains("*"));
747
    }
748
749
    #[test]
750
    fn test_transpile_binary_division() {
751
        let transpiler = create_transpiler();
752
        let code = "15 / 3";
753
        let mut parser = Parser::new(code);
754
        let ast = parser.parse().unwrap();
755
        
756
        let result = transpiler.transpile(&ast).unwrap();
757
        let rust_str = result.to_string();
758
        
759
        assert!(rust_str.contains("15") && rust_str.contains("3"));
760
        assert!(rust_str.contains("/"));
761
    }
762
763
    #[test]
764
    fn test_transpile_binary_modulo() {
765
        let transpiler = create_transpiler();
766
        let code = "10 % 3";
767
        let mut parser = Parser::new(code);
768
        let ast = parser.parse().unwrap();
769
        
770
        let result = transpiler.transpile(&ast).unwrap();
771
        let rust_str = result.to_string();
772
        
773
        assert!(rust_str.contains("10") && rust_str.contains("3"));
774
        assert!(rust_str.contains("%"));
775
    }
776
777
    // Note: String concatenation test removed due to parser limitations with string + operator
778
779
    #[test]
780
    fn test_transpile_comparison_operators() {
781
        let operators = vec!["<", ">", "<=", ">=", "==", "!="];
782
        
783
        for op in operators {
784
            let transpiler = create_transpiler();
785
            let code = format!("5 {op} 3");
786
            let mut parser = Parser::new(&code);
787
            let ast = parser.parse().unwrap();
788
            
789
            let result = transpiler.transpile(&ast).unwrap();
790
            let rust_str = result.to_string();
791
            
792
            assert!(rust_str.contains("5") && rust_str.contains("3"), 
793
                   "Failed for operator {op}: {rust_str}");
794
        }
795
    }
796
797
    #[test]
798
    fn test_transpile_logical_operators() {
799
        let operators = vec!["&&", "||"];
800
        
801
        for op in operators {
802
            let transpiler = create_transpiler();
803
            let code = format!("true {op} false");
804
            let mut parser = Parser::new(&code);
805
            let ast = parser.parse().unwrap();
806
            
807
            let result = transpiler.transpile(&ast).unwrap();
808
            let rust_str = result.to_string();
809
            
810
            assert!(rust_str.contains("true") && rust_str.contains("false"),
811
                   "Failed for operator {op}: {rust_str}");
812
        }
813
    }
814
815
    #[test]
816
    fn test_transpile_unary_operators() {
817
        let test_cases = vec![
818
            ("!true", "true"),
819
            ("-5", "5"),
820
        ];
821
        
822
        for (code, expected) in test_cases {
823
            let transpiler = create_transpiler();
824
            let mut parser = Parser::new(code);
825
            let ast = parser.parse().unwrap();
826
            
827
            let result = transpiler.transpile(&ast).unwrap();
828
            let rust_str = result.to_string();
829
            
830
            assert!(rust_str.contains(expected),
831
                   "Failed for {code}: {rust_str}");
832
        }
833
    }
834
835
    #[test]
836
    fn test_transpile_identifier() {
837
        let transpiler = create_transpiler();
838
        let code = "variable_name";
839
        let mut parser = Parser::new(code);
840
        let ast = parser.parse().unwrap();
841
        
842
        let result = transpiler.transpile(&ast).unwrap();
843
        let rust_str = result.to_string();
844
        
845
        assert!(rust_str.contains("variable_name"));
846
    }
847
848
    #[test]
849
    fn test_transpile_function_call() {
850
        let transpiler = create_transpiler();
851
        let code = "func_name(arg1, arg2)";
852
        let mut parser = Parser::new(code);
853
        let ast = parser.parse().unwrap();
854
        
855
        let result = transpiler.transpile(&ast).unwrap();
856
        let rust_str = result.to_string();
857
        
858
        assert!(rust_str.contains("func_name"));
859
        assert!(rust_str.contains("arg1"));
860
        assert!(rust_str.contains("arg2"));
861
    }
862
863
    #[test]
864
    fn test_transpile_function_call_no_args() {
865
        let transpiler = create_transpiler();
866
        let code = "func_name()";
867
        let mut parser = Parser::new(code);
868
        let ast = parser.parse().unwrap();
869
        
870
        let result = transpiler.transpile(&ast).unwrap();
871
        let rust_str = result.to_string();
872
        
873
        assert!(rust_str.contains("func_name"));
874
        assert!(rust_str.contains("()"));
875
    }
876
877
    #[test]
878
    fn test_transpile_list() {
879
        let transpiler = create_transpiler();
880
        let code = "[1, 2, 3]";
881
        let mut parser = Parser::new(code);
882
        let ast = parser.parse().unwrap();
883
        
884
        let result = transpiler.transpile(&ast).unwrap();
885
        let rust_str = result.to_string();
886
        
887
        assert!(rust_str.contains("vec!") || rust_str.contains("["));
888
        assert!(rust_str.contains("1") && rust_str.contains("2") && rust_str.contains("3"));
889
    }
890
891
    #[test]
892
    fn test_transpile_empty_list() {
893
        let transpiler = create_transpiler();
894
        let code = "[]";
895
        let mut parser = Parser::new(code);
896
        let ast = parser.parse().unwrap();
897
        
898
        let result = transpiler.transpile(&ast).unwrap();
899
        let rust_str = result.to_string();
900
        
901
        assert!(rust_str.contains("vec!") || rust_str.contains("[]"));
902
    }
903
904
    #[test]
905
    fn test_transpile_range() {
906
        let transpiler = create_transpiler();
907
        let code = "1..10";
908
        let mut parser = Parser::new(code);
909
        let ast = parser.parse().unwrap();
910
        
911
        let result = transpiler.transpile(&ast).unwrap();
912
        let rust_str = result.to_string();
913
        
914
        assert!(rust_str.contains("1") && rust_str.contains("10"));
915
        assert!(rust_str.contains("..") || rust_str.contains("range"));
916
    }
917
918
    #[test]
919
    fn test_transpile_inclusive_range() {
920
        let transpiler = create_transpiler();
921
        let code = "1..=10";
922
        let mut parser = Parser::new(code);
923
        let ast = parser.parse().unwrap();
924
        
925
        let result = transpiler.transpile(&ast).unwrap();
926
        let rust_str = result.to_string();
927
        
928
        assert!(rust_str.contains("1") && rust_str.contains("10"));
929
        assert!(rust_str.contains("..=") || rust_str.contains("inclusive"));
930
    }
931
932
    #[test]
933
    fn test_transpile_block_expression() {
934
        let transpiler = create_transpiler();
935
        let code = "{ let x = 5; x }";
936
        let mut parser = Parser::new(code);
937
        let ast = parser.parse().unwrap();
938
        
939
        let result = transpiler.transpile(&ast).unwrap();
940
        let rust_str = result.to_string();
941
        
942
        assert!(rust_str.contains("{"));
943
        assert!(rust_str.contains("}"));
944
        assert!(rust_str.contains("let"));
945
        assert!(rust_str.contains("x"));
946
        assert!(rust_str.contains("5"));
947
    }
948
949
    #[test]
950
    fn test_definitely_string_detection() {
951
        // String literals should be definitely strings
952
        let code1 = "\"hello\"";
953
        let mut parser1 = Parser::new(code1);
954
        let ast1 = parser1.parse().unwrap();
955
        if let ExprKind::Block(exprs) = &ast1.kind {
956
            if let Some(expr) = exprs.first() {
957
                assert!(Transpiler::is_definitely_string(expr));
958
            }
959
        }
960
        
961
        // Numbers should not be definitely strings
962
        let code2 = "42";
963
        let mut parser2 = Parser::new(code2);
964
        let ast2 = parser2.parse().unwrap();
965
        if let ExprKind::Block(exprs) = &ast2.kind {
966
            if let Some(expr) = exprs.first() {
967
                assert!(!Transpiler::is_definitely_string(expr));
968
            }
969
        }
970
    }
971
972
    #[test]
973
    fn test_complex_nested_expressions() {
974
        let transpiler = create_transpiler();
975
        let code = "(5 + 3) * (10 - 2)";
976
        let mut parser = Parser::new(code);
977
        let ast = parser.parse().unwrap();
978
        
979
        let result = transpiler.transpile(&ast).unwrap();
980
        let rust_str = result.to_string();
981
        
982
        // Should handle nested arithmetic with parentheses
983
        assert!(rust_str.contains("5") && rust_str.contains("3"));
984
        assert!(rust_str.contains("10") && rust_str.contains("2"));
985
        assert!(rust_str.contains("+") && rust_str.contains("-") && rust_str.contains("*"));
986
    }
987
988
    #[test]
989
    fn test_integer_literal_size_handling() {
990
        // Small integers
991
        assert_eq!(
992
            Transpiler::transpile_integer(42).to_string(),
993
            "42i32"
994
        );
995
        
996
        // Large integers  
997
        #[allow(clippy::unreadable_literal)]
998
        let large_int = 9223372036854775807;
999
        assert_eq!(
1000
            Transpiler::transpile_integer(large_int).to_string(),
1001
            "9223372036854775807i64"
1002
        );
1003
        
1004
        // Negative integers
1005
        assert_eq!(
1006
            Transpiler::transpile_integer(-42).to_string(),
1007
            "- 42i32"
1008
        );
1009
    }
1010
1011
    #[test]
1012
    fn test_method_call_transpilation() {
1013
        let transpiler = create_transpiler();
1014
        let code = "obj.method(arg)";
1015
        let mut parser = Parser::new(code);
1016
        let ast = parser.parse().unwrap();
1017
        
1018
        let result = transpiler.transpile(&ast).unwrap();
1019
        let rust_str = result.to_string();
1020
        
1021
        assert!(rust_str.contains("obj"));
1022
        assert!(rust_str.contains("method"));
1023
        assert!(rust_str.contains("arg"));
1024
    }
1025
1026
    #[test]
1027
    fn test_string_interpolation_transpilation() {
1028
        let transpiler = create_transpiler();
1029
        let code = "f\"Hello {name}\"";
1030
        let mut parser = Parser::new(code);
1031
        let ast = parser.parse().unwrap();
1032
        
1033
        let result = transpiler.transpile(&ast).unwrap();
1034
        let rust_str = result.to_string();
1035
        
1036
        // String interpolation should use format!
1037
        assert!(rust_str.contains("format!") || rust_str.contains("Hello"));
1038
        assert!(rust_str.contains("name"));
1039
    }
1040
}