## 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
    }