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/type_inference.rs
Line
Count
Source
1
//! Type inference helpers for transpiler
2
//! 
3
//! This module provides intelligent type inference by analyzing how
4
//! parameters and expressions are used in function bodies.
5
6
use crate::frontend::ast::{Expr, ExprKind, BinaryOp, Literal};
7
8
/// Analyzes if a parameter is used as an argument to a function that takes i32
9
31
pub fn is_param_used_as_function_argument(param_name: &str, expr: &Expr) -> bool {
10
31
    match &expr.kind {
11
3
        ExprKind::Call { func, args } => {
12
            // Check if the function being called has the parameter as an argument
13
3
            if let ExprKind::Identifier(_func_name) = &func.kind {
14
                // If this is calling another function parameter (higher-order function)
15
5
                for 
arg3
in args {
16
3
                    if let ExprKind::Identifier(arg_name) = &arg.kind {
17
3
                        if arg_name == param_name {
18
1
                            return true; // Parameter is used as argument to function call
19
2
                        }
20
0
                    }
21
                }
22
0
            }
23
            // Recursively check arguments
24
2
            args.iter().any(|arg| is_param_used_as_function_argument(param_name, arg))
25
        }
26
6
        ExprKind::Block(exprs) => {
27
8
            
exprs.iter()6
.
any6
(|e| is_param_used_as_function_argument(param_name, e))
28
        }
29
1
        ExprKind::If { condition, then_branch, else_branch } => {
30
1
            is_param_used_as_function_argument(param_name, condition) ||
31
1
            is_param_used_as_function_argument(param_name, then_branch) ||
32
1
            else_branch.as_ref().is_some_and(|e| is_param_used_as_function_argument(param_name, e))
33
        }
34
3
        ExprKind::Let { value, body, .. } => {
35
3
            is_param_used_as_function_argument(param_name, value) ||
36
3
            is_param_used_as_function_argument(param_name, body)
37
        }
38
0
        ExprKind::LetPattern { value, body, .. } => {
39
0
            is_param_used_as_function_argument(param_name, value) ||
40
0
            is_param_used_as_function_argument(param_name, body)
41
        }
42
4
        ExprKind::Binary { left, right, .. } => {
43
4
            is_param_used_as_function_argument(param_name, left) ||
44
4
            is_param_used_as_function_argument(param_name, right)
45
        }
46
0
        ExprKind::Unary { operand, .. } => {
47
0
            is_param_used_as_function_argument(param_name, operand)
48
        }
49
14
        _ => false
50
    }
51
31
}
52
53
/// Analyzes if a parameter is used as a function in the given expression
54
83
pub fn is_param_used_as_function(param_name: &str, expr: &Expr) -> bool {
55
83
    match &expr.kind {
56
8
        ExprKind::Call { func, args } => {
57
            // Check if the function being called is the parameter
58
8
            if let ExprKind::Identifier(name) = &func.kind {
59
8
                if name == param_name {
60
2
                    return true;
61
6
                }
62
0
            }
63
            // Recursively check arguments
64
6
            args.iter().any(|arg| is_param_used_as_function(param_name, arg))
65
        }
66
18
        ExprKind::Block(exprs) => {
67
20
            
exprs.iter()18
.
any18
(|e| is_param_used_as_function(param_name, e))
68
        }
69
2
        ExprKind::If { condition, then_branch, else_branch } => {
70
2
            is_param_used_as_function(param_name, condition) ||
71
2
            is_param_used_as_function(param_name, then_branch) ||
72
2
            else_branch.as_ref().is_some_and(|e| is_param_used_as_function(param_name, e))
73
        }
74
3
        ExprKind::Let { value, body, .. } => {
75
3
            is_param_used_as_function(param_name, value) ||
76
3
            is_param_used_as_function(param_name, body)
77
        }
78
16
        ExprKind::Binary { left, right, .. } => {
79
16
            is_param_used_as_function(param_name, left) ||
80
15
            is_param_used_as_function(param_name, right)
81
        }
82
0
        ExprKind::Lambda { body, .. } => {
83
0
            is_param_used_as_function(param_name, body)
84
        }
85
36
        _ => false
86
    }
87
83
}
88
89
90
/// Checks if a parameter is used in numeric operations
91
53
pub fn is_param_used_numerically(param_name: &str, expr: &Expr) -> bool {
92
53
    match &expr.kind {
93
12
        ExprKind::Binary { op, left, right } => {
94
            // Check if this parameter is in a numeric operation
95
12
            let is_potentially_numeric_op = 
matches!2
(op,
96
                BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | 
97
                BinaryOp::Divide | BinaryOp::Modulo
98
            );
99
            
100
12
            if is_potentially_numeric_op {
101
10
                let left_has_param = contains_param(param_name, left);
102
10
                let right_has_param = contains_param(param_name, right);
103
                
104
10
                if left_has_param || 
right_has_param4
{
105
                    // Special case: if it's addition and one side is a string literal,
106
                    // this is string concatenation, not numeric addition
107
8
                    if 
matches!5
(op, BinaryOp::Add) && (
is_string_literal3
(
left3
) ||
is_string_literal2
(
right2
)) {
108
1
                        return false; // String concatenation, not numeric
109
7
                    }
110
7
                    return true;
111
2
                }
112
2
            }
113
            
114
            // Recursively check
115
4
            is_param_used_numerically(param_name, left) ||
116
4
            is_param_used_numerically(param_name, right)
117
        }
118
16
        ExprKind::Block(exprs) => {
119
18
            
exprs.iter()16
.
any16
(|e| is_param_used_numerically(param_name, e))
120
        }
121
2
        ExprKind::If { condition, then_branch, else_branch } => {
122
2
            is_param_used_numerically(param_name, condition) ||
123
2
            is_param_used_numerically(param_name, then_branch) ||
124
2
            else_branch.as_ref().is_some_and(|e| is_param_used_numerically(param_name, e))
125
        }
126
3
        ExprKind::Let { value, body, .. } => {
127
3
            is_param_used_numerically(param_name, value) ||
128
3
            is_param_used_numerically(param_name, body)
129
        }
130
3
        ExprKind::Call { args, .. } => {
131
3
            args.iter().any(|arg| is_param_used_numerically(param_name, arg))
132
        }
133
17
        _ => false
134
    }
135
53
}
136
137
/// Helper to check if an expression contains a specific parameter
138
38
fn contains_param(param_name: &str, expr: &Expr) -> bool {
139
38
    match &expr.kind {
140
21
        ExprKind::Identifier(name) => name == param_name,
141
4
        ExprKind::Binary { left, right, .. } => {
142
4
            contains_param(param_name, left) || 
contains_param2
(
param_name2
,
right2
)
143
        }
144
0
        ExprKind::Block(exprs) => {
145
0
            exprs.iter().any(|e| contains_param(param_name, e))
146
        }
147
6
        ExprKind::Call { func, args } => {
148
6
            contains_param(param_name, func) ||
149
6
            args.iter().any(|arg| contains_param(param_name, arg))
150
        }
151
7
        _ => false
152
    }
153
38
}
154
155
/// Helper to check if an expression is a string literal
156
5
fn is_string_literal(expr: &Expr) -> bool {
157
4
    matches!(
&expr.kind1
, ExprKind::Literal(Literal::String(_)))
158
5
}
159
160
#[cfg(test)]
161
mod tests {
162
    use super::*;
163
    use crate::Parser;
164
165
    /// Checks if an expression contains numeric operations (test helper)
166
0
    fn contains_numeric_operations(expr: &Expr) -> bool {
167
0
        match &expr.kind {
168
0
            ExprKind::Binary { op, left, right } => {
169
                // Check for numeric operators
170
0
                matches!(op, 
171
                    BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | 
172
                    BinaryOp::Divide | BinaryOp::Modulo | BinaryOp::Less | 
173
                    BinaryOp::Greater | BinaryOp::LessEqual | BinaryOp::GreaterEqual
174
0
                ) || contains_numeric_operations(left) || contains_numeric_operations(right)
175
            }
176
0
            ExprKind::Block(exprs) => {
177
0
                exprs.iter().any(contains_numeric_operations)
178
            }
179
0
            ExprKind::If { then_branch, else_branch, .. } => {
180
0
                contains_numeric_operations(then_branch) ||
181
0
                else_branch.as_ref().is_some_and(|e| contains_numeric_operations(e))
182
            }
183
0
            ExprKind::Let { value, body, .. } => {
184
0
                contains_numeric_operations(value) || contains_numeric_operations(body)
185
            }
186
0
            ExprKind::Call { args, .. } => {
187
0
                args.iter().any(contains_numeric_operations)
188
            }
189
0
            ExprKind::Lambda { body, .. } => {
190
0
                contains_numeric_operations(body)
191
            }
192
0
            _ => false
193
        }
194
0
    }
195
196
    #[test]
197
1
    fn test_detects_function_parameter() {
198
1
        let code = "fun test() { f(x) }";
199
1
        let mut parser = Parser::new(code);
200
1
        let ast = parser.parse().unwrap();
201
        
202
        // Find the function body
203
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
204
0
            for expr in exprs {
205
0
                if let ExprKind::Function { body, .. } = &expr.kind {
206
0
                    assert!(is_param_used_as_function("f", body));
207
0
                    assert!(!is_param_used_as_function("x", body));
208
0
                }
209
            }
210
1
        }
211
1
    }
212
213
    #[test]
214
1
    fn test_detects_numeric_operations() {
215
1
        let code = "fun test(x) { x * 2 }";
216
1
        let mut parser = Parser::new(code);
217
1
        let ast = parser.parse().unwrap();
218
        
219
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
220
0
            for expr in exprs {
221
0
                if let ExprKind::Function { body, .. } = &expr.kind {
222
0
                    assert!(contains_numeric_operations(body));
223
0
                    assert!(is_param_used_numerically("x", body));
224
0
                }
225
            }
226
1
        }
227
1
    }
228
229
    #[test]
230
1
    fn test_detects_nested_function_call() {
231
1
        let code = "fun test() { g(f(x)) }";
232
1
        let mut parser = Parser::new(code);
233
1
        let ast = parser.parse().unwrap();
234
        
235
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
236
0
            for expr in exprs {
237
0
                if let ExprKind::Function { body, .. } = &expr.kind {
238
0
                    assert!(is_param_used_as_function("f", body));
239
0
                    assert!(is_param_used_as_function("g", body));
240
0
                    assert!(!is_param_used_as_function("x", body));
241
0
                }
242
            }
243
1
        }
244
1
    }
245
246
    #[test]
247
1
    fn test_detects_function_in_if_branch() {
248
1
        let code = "fun test(p) { if (true) { p(5) } else { 0 } }";
249
1
        let mut parser = Parser::new(code);
250
1
        let ast = parser.parse().unwrap();
251
        
252
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
253
0
            for expr in exprs {
254
0
                if let ExprKind::Function { body, .. } = &expr.kind {
255
0
                    assert!(is_param_used_as_function("p", body));
256
0
                }
257
            }
258
1
        }
259
1
    }
260
261
    #[test]
262
1
    fn test_detects_function_in_let_body() {
263
1
        let code = "fun test(f) { let x = 5; f(x) }";
264
1
        let mut parser = Parser::new(code);
265
1
        let ast = parser.parse().unwrap();
266
        
267
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
268
0
            for expr in exprs {
269
0
                if let ExprKind::Function { body, .. } = &expr.kind {
270
0
                    assert!(is_param_used_as_function("f", body));
271
0
                }
272
            }
273
1
        }
274
1
    }
275
276
    #[test]
277
1
    fn test_detects_function_in_lambda() {
278
1
        let code = "fun test(f) { (x) => f(x) }";
279
1
        let mut parser = Parser::new(code);
280
1
        let ast = parser.parse().unwrap();
281
        
282
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
283
0
            for expr in exprs {
284
0
                if let ExprKind::Function { body, .. } = &expr.kind {
285
0
                    assert!(is_param_used_as_function("f", body));
286
0
                }
287
            }
288
1
        }
289
1
    }
290
291
    #[test]
292
1
    fn test_detects_numeric_in_addition() {
293
1
        let code = "fun test(n) { n + 10 }";
294
1
        let mut parser = Parser::new(code);
295
1
        let ast = parser.parse().unwrap();
296
        
297
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
298
0
            for expr in exprs {
299
0
                if let ExprKind::Function { body, .. } = &expr.kind {
300
0
                    assert!(is_param_used_numerically("n", body));
301
0
                    assert!(contains_numeric_operations(body));
302
0
                }
303
            }
304
1
        }
305
1
    }
306
307
    #[test]
308
1
    fn test_detects_numeric_in_subtraction() {
309
1
        let code = "fun test(n) { n - 5 }";
310
1
        let mut parser = Parser::new(code);
311
1
        let ast = parser.parse().unwrap();
312
        
313
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
314
0
            for expr in exprs {
315
0
                if let ExprKind::Function { body, .. } = &expr.kind {
316
0
                    assert!(is_param_used_numerically("n", body));
317
0
                    assert!(contains_numeric_operations(body));
318
0
                }
319
            }
320
1
        }
321
1
    }
322
323
    #[test]
324
1
    fn test_detects_numeric_in_division() {
325
1
        let code = "fun test(n) { n / 2 }";
326
1
        let mut parser = Parser::new(code);
327
1
        let ast = parser.parse().unwrap();
328
        
329
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
330
0
            for expr in exprs {
331
0
                if let ExprKind::Function { body, .. } = &expr.kind {
332
0
                    assert!(is_param_used_numerically("n", body));
333
0
                    assert!(contains_numeric_operations(body));
334
0
                }
335
            }
336
1
        }
337
1
    }
338
339
    #[test]
340
1
    fn test_detects_numeric_in_modulo() {
341
1
        let code = "fun test(n) { n % 3 }";
342
1
        let mut parser = Parser::new(code);
343
1
        let ast = parser.parse().unwrap();
344
        
345
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
346
0
            for expr in exprs {
347
0
                if let ExprKind::Function { body, .. } = &expr.kind {
348
0
                    assert!(is_param_used_numerically("n", body));
349
0
                    assert!(contains_numeric_operations(body));
350
0
                }
351
            }
352
1
        }
353
1
    }
354
355
    #[test]
356
1
    fn test_detects_numeric_in_comparison() {
357
1
        let code = "fun test(n) { n > 5 }";
358
1
        let mut parser = Parser::new(code);
359
1
        let ast = parser.parse().unwrap();
360
        
361
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
362
0
            for expr in exprs {
363
0
                if let ExprKind::Function { body, .. } = &expr.kind {
364
0
                    assert!(!is_param_used_numerically("n", body)); // Comparisons don't count as numeric
365
0
                    assert!(contains_numeric_operations(body)); // But the expression contains numeric ops
366
0
                }
367
            }
368
1
        }
369
1
    }
370
371
    #[test]
372
1
    fn test_no_function_no_numeric() {
373
1
        let code = "fun test(s) { s + \" world\" }";
374
1
        let mut parser = Parser::new(code);
375
1
        let ast = parser.parse().unwrap();
376
        
377
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
378
0
            for expr in exprs {
379
0
                if let ExprKind::Function { body, .. } = &expr.kind {
380
0
                    assert!(!is_param_used_as_function("s", body));
381
0
                    assert!(!is_param_used_numerically("s", body));
382
0
                }
383
            }
384
1
        }
385
1
    }
386
387
    #[test]
388
1
    fn test_contains_param_helper() {
389
1
        let code = "fun test(x) { x }";
390
1
        let mut parser = Parser::new(code);
391
1
        let ast = parser.parse().unwrap();
392
        
393
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
394
0
            for expr in exprs {
395
0
                if let ExprKind::Function { body, .. } = &expr.kind {
396
0
                    assert!(contains_param("x", body));
397
0
                    assert!(!contains_param("y", body));
398
0
                }
399
            }
400
1
        }
401
1
    }
402
403
    #[test]
404
1
    fn test_contains_param_in_binary() {
405
1
        let code = "fun test(x, y) { x + y }";
406
1
        let mut parser = Parser::new(code);
407
1
        let ast = parser.parse().unwrap();
408
        
409
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
410
0
            for expr in exprs {
411
0
                if let ExprKind::Function { body, .. } = &expr.kind {
412
0
                    assert!(contains_param("x", body));
413
0
                    assert!(contains_param("y", body));
414
0
                    assert!(!contains_param("z", body));
415
0
                }
416
            }
417
1
        }
418
1
    }
419
420
    #[test]
421
1
    fn test_complex_nested_detection() {
422
1
        let code = "fun test(f, g, x) { f(g(x * 2)) }";
423
1
        let mut parser = Parser::new(code);
424
1
        let ast = parser.parse().unwrap();
425
        
426
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
427
0
            for expr in exprs {
428
0
                if let ExprKind::Function { body, .. } = &expr.kind {
429
0
                    assert!(is_param_used_as_function("f", body));
430
0
                    assert!(is_param_used_as_function("g", body));
431
0
                    assert!(!is_param_used_as_function("x", body));
432
0
                    assert!(is_param_used_numerically("x", body));
433
0
                }
434
            }
435
1
        }
436
1
    }
437
438
    #[test]
439
1
    fn test_string_concatenation_not_numeric() {
440
1
        let code = "fun greet(name) { \"Hello \" + name }";
441
1
        let mut parser = Parser::new(code);
442
1
        let ast = parser.parse().unwrap();
443
        
444
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
445
0
            for expr in exprs {
446
0
                if let ExprKind::Function { body, .. } = &expr.kind {
447
                    // name should NOT be considered numeric in string concatenation
448
0
                    assert!(!is_param_used_numerically("name", body));
449
0
                    assert!(!is_param_used_as_function("name", body));
450
0
                }
451
            }
452
1
        }
453
1
    }
454
455
    #[test]
456
1
    fn test_string_literal_helper() {
457
1
        let code = "fun test() { \"hello\" }";
458
1
        let mut parser = Parser::new(code);
459
1
        let ast = parser.parse().unwrap();
460
        
461
1
        if let ExprKind::Block(
exprs0
) = &ast.kind {
462
0
            for expr in exprs {
463
0
                if let ExprKind::Function { body, .. } = &expr.kind {
464
0
                    assert!(is_string_literal(body));
465
0
                }
466
            }
467
1
        }
468
1
    }
469
}