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