Coverage Report

Created: 2025-09-05 15: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
444
    pub fn transpile_literal(lit: &Literal) -> TokenStream {
15
444
        match lit {
16
306
            Literal::Integer(i) => Self::transpile_integer(*i),
17
20
            Literal::Float(f) => quote! { #f },
18
20
            Literal::Unit => quote! { () },
19
98
            _ => Self::transpile_simple_literal(lit),
20
        }
21
444
    }
22
23
98
    fn transpile_simple_literal(lit: &Literal) -> TokenStream {
24
98
        match lit {
25
65
            Literal::String(s) => quote! { #s },
26
33
            Literal::Bool(b) => quote! { #b },
27
0
            Literal::Char(c) => quote! { #c },
28
0
            _ => unreachable!(),
29
        }
30
98
    }
31
32
309
    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
309
        if let Ok(
i32_val239
) = i32::try_from(i) {
36
            // Use i32 suffix for clarity and to match struct field types
37
239
            let literal = proc_macro2::Literal::i32_suffixed(i32_val);
38
239
            quote! { #literal }
39
        } else {
40
            // For large integers, we need i64 suffix
41
70
            let literal = proc_macro2::Literal::i64_suffixed(i);
42
70
            quote! { #literal }
43
        }
44
309
    }
45
46
    /// Transpiles string interpolation
47
    ///
48
    /// # Errors
49
    /// Returns an error if expression transpilation fails
50
5
    pub fn transpile_string_interpolation(&self, parts: &[StringPart]) -> Result<TokenStream> {
51
5
        if parts.is_empty() {
52
0
            return Ok(quote! { "" });
53
5
        }
54
55
5
        let mut format_string = String::new();
56
5
        let mut args = Vec::new();
57
58
10
        for 
part5
in parts {
59
5
            match part {
60
5
                StringPart::Text(s) => {
61
5
                    // Escape any format specifiers in literal parts
62
5
                    format_string.push_str(&s.replace('{', "{{").replace('}', "}}"));
63
5
                }
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
5
        Ok(quote! {
81
            format!(#format_string #(, #args)*)
82
        })
83
5
    }
84
85
    /// Transpiles binary operations
86
151
    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
151
        if op == BinaryOp::Add && (
Self::is_definitely_string52
(
left52
) ||
Self::is_definitely_string51
(
right51
)) {
90
1
            return self.transpile_string_concatenation(left, right);
91
150
        }
92
93
        // Transpile operands with precedence-aware parentheses
94
150
        let left_tokens = self.transpile_expr_with_precedence(left, op, true)
?0
;
95
150
        let right_tokens = self.transpile_expr_with_precedence(right, op, false)
?0
;
96
97
150
        Ok(Self::transpile_binary_op(left_tokens, op, right_tokens))
98
151
    }
99
100
    /// Transpile expression with precedence-aware parentheses
101
    /// 
102
    /// Adds parentheses around sub-expressions when needed to preserve precedence
103
300
    fn transpile_expr_with_precedence(&self, expr: &Expr, parent_op: BinaryOp, is_left_operand: bool) -> Result<TokenStream> {
104
300
        let tokens = self.transpile_expr(expr)
?0
;
105
        
106
        // Check if we need parentheses
107
300
        if let ExprKind::Binary { op: 
child_op30
, .. } = &expr.kind {
108
30
            let parent_prec = Self::get_operator_precedence(parent_op);
109
30
            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
30
            let needs_parens = child_prec < parent_prec ||
114
21
                (!is_left_operand && 
child_prec == parent_prec10
&&
Self::is_right_associative1
(
parent_op1
));
115
            
116
30
            if needs_parens {
117
9
                return Ok(quote! { (#tokens) });
118
21
            }
119
270
        }
120
        
121
291
        Ok(tokens)
122
300
    }
123
124
    /// Get operator precedence (higher number = higher precedence)
125
60
    fn get_operator_precedence(op: BinaryOp) -> i32 {
126
60
        match op {
127
4
            BinaryOp::Or => 10,
128
5
            BinaryOp::And => 20,
129
1
            BinaryOp::Equal | BinaryOp::NotEqual => 30,
130
6
            BinaryOp::Less | BinaryOp::LessEqual | BinaryOp::Greater | BinaryOp::GreaterEqual => 40,
131
18
            BinaryOp::Add | BinaryOp::Subtract => 50,
132
13
            BinaryOp::Multiply | BinaryOp::Divide | BinaryOp::Modulo => 60,
133
2
            BinaryOp::Power => 70,
134
11
            _ => 0, // Default for other operators
135
        }
136
60
    }
137
    
138
    /// Check if operator is right-associative
139
1
    fn is_right_associative(op: BinaryOp) -> bool {
140
1
        matches!(op, BinaryOp::Power) // Only power is right-associative in most languages
141
1
    }
142
143
150
    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
150
        match op {
150
            // Arithmetic operations
151
            Add | Subtract | Multiply | Divide | Modulo | Power => {
152
90
                Self::transpile_arithmetic_op(left, op, right)
153
            }
154
            // Comparison operations
155
            Equal | NotEqual | Less | LessEqual | Greater | GreaterEqual => {
156
34
                Self::transpile_comparison_op(left, op, right)
157
            }
158
            // Logical operations
159
10
            And | Or | NullCoalesce => Self::transpile_logical_op(left, op, right),
160
            // Bitwise operations
161
            BitwiseAnd | BitwiseOr | BitwiseXor | LeftShift => {
162
16
                Self::transpile_bitwise_op(left, op, right)
163
            }
164
        }
165
150
    }
166
167
90
    fn transpile_arithmetic_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
168
        use BinaryOp::{Add, Divide, Modulo, Multiply, Power, Subtract};
169
90
        match op {
170
            Add | Subtract | Multiply | Divide | Modulo => {
171
86
                Self::transpile_basic_arithmetic(left, op, right)
172
            }
173
4
            Power => quote! { #left.pow(#right) },
174
0
            _ => unreachable!(),
175
        }
176
90
    }
177
178
86
    fn transpile_basic_arithmetic(
179
86
        left: TokenStream,
180
86
        op: BinaryOp,
181
86
        right: TokenStream,
182
86
    ) -> TokenStream {
183
        // Reduce complexity by splitting into smaller functions
184
86
        match op {
185
51
            BinaryOp::Add => quote! { #left + #right },
186
10
            BinaryOp::Subtract => quote! { #left - #right },
187
16
            BinaryOp::Multiply => quote! { #left * #right },
188
9
            _ => Self::transpile_division_mod(left, op, right),
189
        }
190
86
    }
191
192
9
    fn transpile_division_mod(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
193
9
        match op {
194
4
            BinaryOp::Divide => quote! { #left / #right },
195
5
            BinaryOp::Modulo => quote! { #left % #right },
196
0
            _ => unreachable!(),
197
        }
198
9
    }
199
200
34
    fn transpile_comparison_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
201
        use BinaryOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual};
202
34
        match op {
203
8
            Equal | NotEqual => Self::transpile_equality(left, op, right),
204
26
            Less | LessEqual | Greater | GreaterEqual => Self::transpile_ordering(left, op, right),
205
0
            _ => unreachable!(),
206
        }
207
34
    }
208
209
8
    fn transpile_equality(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
210
8
        match op {
211
5
            BinaryOp::Equal => quote! { #left == #right },
212
3
            BinaryOp::NotEqual => quote! { #left != #right },
213
0
            _ => unreachable!(),
214
        }
215
8
    }
216
217
26
    fn transpile_ordering(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
218
26
        match op {
219
6
            BinaryOp::Less => quote! { #left < #right },
220
4
            BinaryOp::LessEqual => quote! { #left <= #right },
221
16
            _ => Self::transpile_greater_ops(left, op, right),
222
        }
223
26
    }
224
225
16
    fn transpile_greater_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
226
16
        match op {
227
8
            BinaryOp::Greater => quote! { #left > #right },
228
8
            BinaryOp::GreaterEqual => quote! { #left >= #right },
229
0
            _ => unreachable!(),
230
        }
231
16
    }
232
233
10
    fn transpile_logical_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
234
10
        match op {
235
5
            BinaryOp::And => quote! { #left && #right },
236
5
            BinaryOp::Or => quote! { #left || #right },
237
0
            _ => unreachable!(),
238
        }
239
10
    }
240
241
16
    fn transpile_bitwise_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
242
        use BinaryOp::{BitwiseAnd, BitwiseOr, BitwiseXor};
243
16
        match op {
244
6
            BitwiseAnd => quote! { #left & #right },
245
1
            BitwiseOr => quote! { #left | #right },
246
3
            BitwiseXor => quote! { #left ^ #right },
247
6
            _ => Self::transpile_shift_ops(left, op, right),
248
        }
249
16
    }
250
251
6
    fn transpile_shift_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
252
6
        match op {
253
6
            BinaryOp::LeftShift => quote! { #left << #right },
254
0
            _ => unreachable!(),
255
        }
256
6
    }
257
258
    /// Transpiles unary operations  
259
50
    pub fn transpile_unary(&self, op: UnaryOp, operand: &Expr) -> Result<TokenStream> {
260
50
        let operand_tokens = self.transpile_expr(operand)
?0
;
261
262
50
        Ok(match op {
263
23
            UnaryOp::Not | UnaryOp::BitwiseNot => quote! { !#operand_tokens },
264
12
            UnaryOp::Negate => quote! { -#operand_tokens },
265
15
            UnaryOp::Reference => quote! { &#operand_tokens },
266
        })
267
50
    }
268
269
270
    /// Transpiles await expressions
271
1
    pub fn transpile_await(&self, expr: &Expr) -> Result<TokenStream> {
272
1
        let expr_tokens = self.transpile_expr(expr)
?0
;
273
1
        Ok(quote! { #expr_tokens.await })
274
1
    }
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
2
    pub fn transpile_field_access(&self, object: &Expr, field: &str) -> Result<TokenStream> {
292
        use crate::frontend::ast::ExprKind;
293
        
294
2
        let obj_tokens = self.transpile_expr(object)
?0
;
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
1
                let field_ident = format_ident!("{}", field);
309
1
                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
1
                Ok(quote! { 
319
1
                    #obj_tokens.get(#field)
320
1
                        .cloned()
321
1
                        .unwrap_or_else(|| panic!("Field '{}' not found", #field))
322
1
                })
323
            }
324
        }
325
2
    }
326
327
    /// Transpiles index access (array[index])
328
1
    pub fn transpile_index_access(&self, object: &Expr, index: &Expr) -> Result<TokenStream> {
329
        use crate::frontend::ast::{ExprKind, Literal};
330
        
331
1
        let obj_tokens = self.transpile_expr(object)
?0
;
332
1
        let index_tokens = self.transpile_expr(index)
?0
;
333
        
334
        // Smart index access: HashMap.get() for string keys, array indexing for numeric
335
1
        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
1
                Ok(quote! { #obj_tokens[#index_tokens as usize] })
347
            }
348
        }
349
1
    }
350
351
    /// Transpiles slice access (array[start:end])
352
2
    pub fn transpile_slice(&self, object: &Expr, start: Option<&Expr>, end: Option<&Expr>) -> Result<TokenStream> {
353
2
        let obj_tokens = self.transpile_expr(object)
?0
;
354
        
355
2
        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
2
            (Some(start), None) => {
366
                // Slice to end [start..]
367
2
                let start_tokens = self.transpile_expr(start)
?0
;
368
2
                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
2
    }
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
32
    pub fn transpile_list(&self, elements: &[Expr]) -> Result<TokenStream> {
467
32
        let element_tokens: Result<Vec<_>> =
468
68
            
elements32
.
iter32
().
map32
(|e| self.transpile_expr(e)).
collect32
();
469
32
        let element_tokens = element_tokens
?0
;
470
32
        Ok(quote! { vec![#(#element_tokens),*] })
471
32
    }
472
473
    /// Transpiles tuple literals
474
1
    pub fn transpile_tuple(&self, elements: &[Expr]) -> Result<TokenStream> {
475
1
        let element_tokens: Result<Vec<_>> =
476
2
            
elements1
.
iter1
().
map1
(|e| self.transpile_expr(e)).
collect1
();
477
1
        let element_tokens = element_tokens
?0
;
478
1
        Ok(quote! { (#(#element_tokens),*) })
479
1
    }
480
481
    /// Transpiles range expressions
482
4
    pub fn transpile_range(
483
4
        &self,
484
4
        start: &Expr,
485
4
        end: &Expr,
486
4
        inclusive: bool,
487
4
    ) -> Result<TokenStream> {
488
4
        let start_tokens = self.transpile_expr(start)
?0
;
489
4
        let end_tokens = self.transpile_expr(end)
?0
;
490
491
4
        if inclusive {
492
1
            Ok(quote! { #start_tokens..=#end_tokens })
493
        } else {
494
3
            Ok(quote! { #start_tokens..#end_tokens })
495
        }
496
4
    }
497
498
    /// Transpiles object literals
499
4
    pub fn transpile_object_literal(
500
4
        &self,
501
4
        fields: &[crate::frontend::ast::ObjectField],
502
4
    ) -> Result<TokenStream> {
503
4
        let field_tokens = self.collect_hashmap_field_tokens(fields)
?0
;
504
4
        Ok(quote! {
505
            {
506
                let mut map: std::collections::HashMap<String, String> = std::collections::HashMap::new();
507
                #(#field_tokens)*
508
                map
509
            }
510
        })
511
4
    }
512
513
4
    fn collect_hashmap_field_tokens(
514
4
        &self,
515
4
        fields: &[crate::frontend::ast::ObjectField],
516
4
    ) -> Result<Vec<TokenStream>> {
517
        use crate::frontend::ast::ObjectField;
518
4
        let mut field_tokens = Vec::new();
519
520
8
        for 
field4
in fields {
521
4
            let token = match field {
522
4
                ObjectField::KeyValue { key, value } => {
523
4
                    let value_tokens = self.transpile_expr(value)
?0
;
524
4
                    quote! { map.insert(#key.to_string(), (#value_tokens).to_string()); }
525
                }
526
0
                ObjectField::Spread { expr } => {
527
0
                    let expr_tokens = self.transpile_expr(expr)?;
528
                    // For spread syntax, merge the other map into this one
529
0
                    quote! { 
530
                        for (k, v) in #expr_tokens {
531
                            map.insert(k, v);
532
                        }
533
                    }
534
                }
535
            };
536
4
            field_tokens.push(token);
537
        }
538
539
4
        Ok(field_tokens)
540
4
    }
541
542
543
    /// Transpiles struct literals
544
1
    pub fn transpile_struct_literal(
545
1
        &self,
546
1
        name: &str,
547
1
        fields: &[(String, Expr)],
548
1
    ) -> Result<TokenStream> {
549
1
        let struct_name = format_ident!("{}", name);
550
1
        let mut field_tokens = Vec::new();
551
552
3
        for (
field_name2
,
value2
) in fields {
553
2
            let field_ident = format_ident!("{}", field_name);
554
2
            let value_tokens = match &value.kind {
555
                // Convert string literals to String for struct fields
556
0
                ExprKind::Literal(Literal::String(s)) => {
557
0
                    quote! { #s.to_string() }
558
                }
559
2
                _ => self.transpile_expr(value)
?0
,
560
            };
561
2
            field_tokens.push(quote! { #field_ident: #value_tokens });
562
        }
563
564
1
        Ok(quote! {
565
            #struct_name {
566
                #(#field_tokens,)*
567
            }
568
        })
569
1
    }
570
571
    /// Check if an expression is definitely a string (conservative detection)
572
105
    fn is_definitely_string(expr: &Expr) -> bool {
573
65
        match &expr.kind {
574
            // String literals are definitely strings
575
1
            ExprKind::Literal(Literal::String(_)) => true,
576
            // String interpolation is definitely strings
577
0
            ExprKind::StringInterpolation { .. } => true,
578
            // Binary expressions with + that involve strings are string concatenations
579
1
            ExprKind::Binary { op: BinaryOp::Add, left, right } => {
580
1
                Self::is_definitely_string(left) || Self::is_definitely_string(right)
581
            },
582
            // Method calls on strings that return strings
583
0
            ExprKind::MethodCall { receiver, method, .. } => {
584
0
                matches!(method.as_str(), "to_string" | "trim" | "to_uppercase" | "to_lowercase") ||
585
0
                Self::is_definitely_string(receiver)
586
            },
587
            // Variables could be strings, but we can't be sure without type info
588
            // For now, be conservative and don't assume variables are strings
589
26
            ExprKind::Identifier(_) => false,
590
            // Function calls are NOT definitely strings - they could return any type
591
5
            ExprKind::Call { .. } => false,
592
            // Other expressions are not strings
593
72
            _ => false,
594
        }
595
105
    }
596
597
598
    /// Transpile string concatenation using proper Rust string operations
599
1
    fn transpile_string_concatenation(&self, left: &Expr, right: &Expr) -> Result<TokenStream> {
600
1
        let left_tokens = self.transpile_expr(left)
?0
;
601
1
        let right_tokens = self.transpile_expr(right)
?0
;
602
        
603
        // Use format! with proper string handling - convert both to strings to avoid type mismatches
604
        // This avoids the String + String issue in Rust by using format! exclusively
605
1
        Ok(quote! { format!("{}{}", #left_tokens, #right_tokens) })
606
1
    }
607
}
608
609
#[cfg(test)]
610
#[allow(clippy::single_char_pattern)]
611
mod tests {
612
    use super::*;
613
    use crate::{Parser, frontend::ast::ExprKind};
614
615
31
    fn create_transpiler() -> Transpiler {
616
31
        Transpiler::new()
617
31
    }
618
619
    #[test]
620
1
    fn test_transpile_integer_literal() {
621
1
        let transpiler = create_transpiler();
622
1
        let code = "42";
623
1
        let mut parser = Parser::new(code);
624
1
        let ast = parser.parse().unwrap();
625
        
626
1
        let result = transpiler.transpile(&ast).unwrap();
627
1
        let rust_str = result.to_string();
628
        
629
1
        assert!(rust_str.contains("42"));
630
1
    }
631
632
    #[test]
633
1
    fn test_transpile_float_literal() {
634
1
        let transpiler = create_transpiler();
635
1
        let code = "3.14";
636
1
        let mut parser = Parser::new(code);
637
1
        let ast = parser.parse().unwrap();
638
        
639
1
        let result = transpiler.transpile(&ast).unwrap();
640
1
        let rust_str = result.to_string();
641
        
642
1
        assert!(rust_str.contains("3.14"));
643
1
    }
644
645
    #[test]
646
1
    fn test_transpile_string_literal() {
647
1
        let transpiler = create_transpiler();
648
1
        let code = "\"hello\"";
649
1
        let mut parser = Parser::new(code);
650
1
        let ast = parser.parse().unwrap();
651
        
652
1
        let result = transpiler.transpile(&ast).unwrap();
653
1
        let rust_str = result.to_string();
654
        
655
1
        assert!(rust_str.contains("hello"));
656
1
    }
657
658
    #[test]
659
1
    fn test_transpile_boolean_literal() {
660
1
        let transpiler = create_transpiler();
661
1
        let code = "true";
662
1
        let mut parser = Parser::new(code);
663
1
        let ast = parser.parse().unwrap();
664
        
665
1
        let result = transpiler.transpile(&ast).unwrap();
666
1
        let rust_str = result.to_string();
667
        
668
1
        assert!(rust_str.contains("true"));
669
1
    }
670
671
    #[test]
672
1
    fn test_transpile_unit_literal() {
673
1
        let transpiler = create_transpiler();
674
1
        let code = "()";
675
1
        let mut parser = Parser::new(code);
676
1
        let ast = parser.parse().unwrap();
677
        
678
1
        let result = transpiler.transpile(&ast).unwrap();
679
1
        let rust_str = result.to_string();
680
        
681
1
        assert!(rust_str.contains("()"));
682
1
    }
683
684
    #[test]
685
1
    fn test_transpile_binary_addition() {
686
1
        let transpiler = create_transpiler();
687
1
        let code = "5 + 3";
688
1
        let mut parser = Parser::new(code);
689
1
        let ast = parser.parse().unwrap();
690
        
691
1
        let result = transpiler.transpile(&ast).unwrap();
692
1
        let rust_str = result.to_string();
693
        
694
1
        assert!(rust_str.contains("5") && rust_str.contains("3"));
695
1
        assert!(rust_str.contains("+"));
696
1
    }
697
698
    #[test]
699
1
    fn test_transpile_binary_subtraction() {
700
1
        let transpiler = create_transpiler();
701
1
        let code = "10 - 4";
702
1
        let mut parser = Parser::new(code);
703
1
        let ast = parser.parse().unwrap();
704
        
705
1
        let result = transpiler.transpile(&ast).unwrap();
706
1
        let rust_str = result.to_string();
707
        
708
1
        assert!(rust_str.contains("10") && rust_str.contains("4"));
709
1
        assert!(rust_str.contains("-"));
710
1
    }
711
712
    #[test]
713
1
    fn test_transpile_binary_multiplication() {
714
1
        let transpiler = create_transpiler();
715
1
        let code = "6 * 7";
716
1
        let mut parser = Parser::new(code);
717
1
        let ast = parser.parse().unwrap();
718
        
719
1
        let result = transpiler.transpile(&ast).unwrap();
720
1
        let rust_str = result.to_string();
721
        
722
1
        assert!(rust_str.contains("6") && rust_str.contains("7"));
723
1
        assert!(rust_str.contains("*"));
724
1
    }
725
726
    #[test]
727
1
    fn test_transpile_binary_division() {
728
1
        let transpiler = create_transpiler();
729
1
        let code = "15 / 3";
730
1
        let mut parser = Parser::new(code);
731
1
        let ast = parser.parse().unwrap();
732
        
733
1
        let result = transpiler.transpile(&ast).unwrap();
734
1
        let rust_str = result.to_string();
735
        
736
1
        assert!(rust_str.contains("15") && rust_str.contains("3"));
737
1
        assert!(rust_str.contains("/"));
738
1
    }
739
740
    #[test]
741
1
    fn test_transpile_binary_modulo() {
742
1
        let transpiler = create_transpiler();
743
1
        let code = "10 % 3";
744
1
        let mut parser = Parser::new(code);
745
1
        let ast = parser.parse().unwrap();
746
        
747
1
        let result = transpiler.transpile(&ast).unwrap();
748
1
        let rust_str = result.to_string();
749
        
750
1
        assert!(rust_str.contains("10") && rust_str.contains("3"));
751
1
        assert!(rust_str.contains("%"));
752
1
    }
753
754
    // Note: String concatenation test removed due to parser limitations with string + operator
755
756
    #[test]
757
1
    fn test_transpile_comparison_operators() {
758
1
        let operators = vec!["<", ">", "<=", ">=", "==", "!="];
759
        
760
7
        for 
op6
in operators {
761
6
            let transpiler = create_transpiler();
762
6
            let code = format!("5 {op} 3");
763
6
            let mut parser = Parser::new(&code);
764
6
            let ast = parser.parse().unwrap();
765
            
766
6
            let result = transpiler.transpile(&ast).unwrap();
767
6
            let rust_str = result.to_string();
768
            
769
6
            assert!(rust_str.contains("5") && rust_str.contains("3"), 
770
0
                   "Failed for operator {op}: {rust_str}");
771
        }
772
1
    }
773
774
    #[test]
775
1
    fn test_transpile_logical_operators() {
776
1
        let operators = vec!["&&", "||"];
777
        
778
3
        for 
op2
in operators {
779
2
            let transpiler = create_transpiler();
780
2
            let code = format!("true {op} false");
781
2
            let mut parser = Parser::new(&code);
782
2
            let ast = parser.parse().unwrap();
783
            
784
2
            let result = transpiler.transpile(&ast).unwrap();
785
2
            let rust_str = result.to_string();
786
            
787
2
            assert!(rust_str.contains("true") && rust_str.contains("false"),
788
0
                   "Failed for operator {op}: {rust_str}");
789
        }
790
1
    }
791
792
    #[test]
793
1
    fn test_transpile_unary_operators() {
794
1
        let test_cases = vec![
795
1
            ("!true", "true"),
796
1
            ("-5", "5"),
797
        ];
798
        
799
3
        for (
code2
,
expected2
) in test_cases {
800
2
            let transpiler = create_transpiler();
801
2
            let mut parser = Parser::new(code);
802
2
            let ast = parser.parse().unwrap();
803
            
804
2
            let result = transpiler.transpile(&ast).unwrap();
805
2
            let rust_str = result.to_string();
806
            
807
2
            assert!(rust_str.contains(expected),
808
0
                   "Failed for {code}: {rust_str}");
809
        }
810
1
    }
811
812
    #[test]
813
1
    fn test_transpile_identifier() {
814
1
        let transpiler = create_transpiler();
815
1
        let code = "variable_name";
816
1
        let mut parser = Parser::new(code);
817
1
        let ast = parser.parse().unwrap();
818
        
819
1
        let result = transpiler.transpile(&ast).unwrap();
820
1
        let rust_str = result.to_string();
821
        
822
1
        assert!(rust_str.contains("variable_name"));
823
1
    }
824
825
    #[test]
826
1
    fn test_transpile_function_call() {
827
1
        let transpiler = create_transpiler();
828
1
        let code = "func_name(arg1, arg2)";
829
1
        let mut parser = Parser::new(code);
830
1
        let ast = parser.parse().unwrap();
831
        
832
1
        let result = transpiler.transpile(&ast).unwrap();
833
1
        let rust_str = result.to_string();
834
        
835
1
        assert!(rust_str.contains("func_name"));
836
1
        assert!(rust_str.contains("arg1"));
837
1
        assert!(rust_str.contains("arg2"));
838
1
    }
839
840
    #[test]
841
1
    fn test_transpile_function_call_no_args() {
842
1
        let transpiler = create_transpiler();
843
1
        let code = "func_name()";
844
1
        let mut parser = Parser::new(code);
845
1
        let ast = parser.parse().unwrap();
846
        
847
1
        let result = transpiler.transpile(&ast).unwrap();
848
1
        let rust_str = result.to_string();
849
        
850
1
        assert!(rust_str.contains("func_name"));
851
1
        assert!(rust_str.contains("()"));
852
1
    }
853
854
    #[test]
855
1
    fn test_transpile_list() {
856
1
        let transpiler = create_transpiler();
857
1
        let code = "[1, 2, 3]";
858
1
        let mut parser = Parser::new(code);
859
1
        let ast = parser.parse().unwrap();
860
        
861
1
        let result = transpiler.transpile(&ast).unwrap();
862
1
        let rust_str = result.to_string();
863
        
864
1
        assert!(rust_str.contains("vec!") || rust_str.contains("["));
865
1
        assert!(rust_str.contains("1") && rust_str.contains("2") && rust_str.contains("3"));
866
1
    }
867
868
    #[test]
869
1
    fn test_transpile_empty_list() {
870
1
        let transpiler = create_transpiler();
871
1
        let code = "[]";
872
1
        let mut parser = Parser::new(code);
873
1
        let ast = parser.parse().unwrap();
874
        
875
1
        let result = transpiler.transpile(&ast).unwrap();
876
1
        let rust_str = result.to_string();
877
        
878
1
        assert!(rust_str.contains("vec!") || rust_str.contains("[]"));
879
1
    }
880
881
    #[test]
882
1
    fn test_transpile_range() {
883
1
        let transpiler = create_transpiler();
884
1
        let code = "1..10";
885
1
        let mut parser = Parser::new(code);
886
1
        let ast = parser.parse().unwrap();
887
        
888
1
        let result = transpiler.transpile(&ast).unwrap();
889
1
        let rust_str = result.to_string();
890
        
891
1
        assert!(rust_str.contains("1") && rust_str.contains("10"));
892
1
        assert!(rust_str.contains("..") || 
rust_str.contains("range")0
);
893
1
    }
894
895
    #[test]
896
1
    fn test_transpile_inclusive_range() {
897
1
        let transpiler = create_transpiler();
898
1
        let code = "1..=10";
899
1
        let mut parser = Parser::new(code);
900
1
        let ast = parser.parse().unwrap();
901
        
902
1
        let result = transpiler.transpile(&ast).unwrap();
903
1
        let rust_str = result.to_string();
904
        
905
1
        assert!(rust_str.contains("1") && rust_str.contains("10"));
906
1
        assert!(rust_str.contains("..=") || 
rust_str.contains("inclusive")0
);
907
1
    }
908
909
    #[test]
910
1
    fn test_transpile_block_expression() {
911
1
        let transpiler = create_transpiler();
912
1
        let code = "{ let x = 5; x }";
913
1
        let mut parser = Parser::new(code);
914
1
        let ast = parser.parse().unwrap();
915
        
916
1
        let result = transpiler.transpile(&ast).unwrap();
917
1
        let rust_str = result.to_string();
918
        
919
1
        assert!(rust_str.contains("{"));
920
1
        assert!(rust_str.contains("}"));
921
1
        assert!(rust_str.contains("let"));
922
1
        assert!(rust_str.contains("x"));
923
1
        assert!(rust_str.contains("5"));
924
1
    }
925
926
    #[test]
927
1
    fn test_definitely_string_detection() {
928
        // String literals should be definitely strings
929
1
        let code1 = "\"hello\"";
930
1
        let mut parser1 = Parser::new(code1);
931
1
        let ast1 = parser1.parse().unwrap();
932
1
        if let ExprKind::Block(
exprs0
) = &ast1.kind {
933
0
            if let Some(expr) = exprs.first() {
934
0
                assert!(Transpiler::is_definitely_string(expr));
935
0
            }
936
1
        }
937
        
938
        // Numbers should not be definitely strings
939
1
        let code2 = "42";
940
1
        let mut parser2 = Parser::new(code2);
941
1
        let ast2 = parser2.parse().unwrap();
942
1
        if let ExprKind::Block(
exprs0
) = &ast2.kind {
943
0
            if let Some(expr) = exprs.first() {
944
0
                assert!(!Transpiler::is_definitely_string(expr));
945
0
            }
946
1
        }
947
1
    }
948
949
    #[test]
950
1
    fn test_complex_nested_expressions() {
951
1
        let transpiler = create_transpiler();
952
1
        let code = "(5 + 3) * (10 - 2)";
953
1
        let mut parser = Parser::new(code);
954
1
        let ast = parser.parse().unwrap();
955
        
956
1
        let result = transpiler.transpile(&ast).unwrap();
957
1
        let rust_str = result.to_string();
958
        
959
        // Should handle nested arithmetic with parentheses
960
1
        assert!(rust_str.contains("5") && rust_str.contains("3"));
961
1
        assert!(rust_str.contains("10") && rust_str.contains("2"));
962
1
        assert!(rust_str.contains("+") && rust_str.contains("-") && rust_str.contains("*"));
963
1
    }
964
965
    #[test]
966
1
    fn test_integer_literal_size_handling() {
967
        // Small integers
968
1
        assert_eq!(
969
1
            Transpiler::transpile_integer(42).to_string(),
970
            "42i32"
971
        );
972
        
973
        // Large integers  
974
        #[allow(clippy::unreadable_literal)]
975
1
        let large_int = 9223372036854775807;
976
1
        assert_eq!(
977
1
            Transpiler::transpile_integer(large_int).to_string(),
978
            "9223372036854775807i64"
979
        );
980
        
981
        // Negative integers
982
1
        assert_eq!(
983
1
            Transpiler::transpile_integer(-42).to_string(),
984
            "- 42i32"
985
        );
986
1
    }
987
988
    #[test]
989
1
    fn test_method_call_transpilation() {
990
1
        let transpiler = create_transpiler();
991
1
        let code = "obj.method(arg)";
992
1
        let mut parser = Parser::new(code);
993
1
        let ast = parser.parse().unwrap();
994
        
995
1
        let result = transpiler.transpile(&ast).unwrap();
996
1
        let rust_str = result.to_string();
997
        
998
1
        assert!(rust_str.contains("obj"));
999
1
        assert!(rust_str.contains("method"));
1000
1
        assert!(rust_str.contains("arg"));
1001
1
    }
1002
1003
    #[test]
1004
1
    fn test_string_interpolation_transpilation() {
1005
1
        let transpiler = create_transpiler();
1006
1
        let code = "f\"Hello {name}\"";
1007
1
        let mut parser = Parser::new(code);
1008
1
        let ast = parser.parse().unwrap();
1009
        
1010
1
        let result = transpiler.transpile(&ast).unwrap();
1011
1
        let rust_str = result.to_string();
1012
        
1013
        // String interpolation should use format!
1014
1
        assert!(rust_str.contains("format!") || rust_str.contains("Hello"));
1015
1
        assert!(rust_str.contains("name"));
1016
1
    }
1017
}