## 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/frontend/parser/functions.rs

    Line
    Count
    Source
    1
    //! Function-related parsing (function definitions, lambdas, calls)
    2
    3
    use super::{ParserState, *};
    4
    use crate::frontend::ast::{DataFrameOp, Literal, Pattern};
    5
    6
    /// # Errors
    7
    ///
    8
    /// Returns an error if the operation fails
    9
    /// # Errors
    10
    ///
    11
    /// Returns an error if the operation fails
    12
    0
    pub fn parse_function(state: &mut ParserState) -> Result<Expr> {
    13
    0
        parse_function_with_visibility(state, false)
    14
    0
    }
    15
    16
    0
    pub fn parse_function_with_visibility(state: &mut ParserState, is_pub: bool) -> Result<Expr> {
    17
    0
        let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume fun
    18
    19
        // Check for async modifier - currently not implemented in lexer
    20
        // When async keyword is added to lexer, this will be:
    21
        // let is_async = state.tokens.check(&Token::Async);
    22
    0
        let is_async = false;
    23
    24
        // Parse function name
    25
    0
        let name = if let Some((Token::Identifier(n), _)) = state.tokens.peek() {
    26
    0
            let name = n.clone();
    27
    0
            state.tokens.advance();
    28
    0
            name
    29
        } else {
    30
    0
            "anonymous".to_string()
    31
        };
    32
    33
        // Parse optional type parameters <T, U, ...>
    34
    0
        let type_params = if matches!(state.tokens.peek(), Some((Token::Less, _))) {
    35
    0
            utils::parse_type_parameters(state)?
    36
        } else {
    37
    0
            Vec::new()
    38
        };
    39
    40
        // Parse parameters
    41
    0
        let params = utils::parse_params(state)?;
    42
    43
        // Parse return type if present
    44
    0
        let return_type = if matches!(state.tokens.peek(), Some((Token::Arrow, _))) {
    45
    0
            state.tokens.advance(); // consume ->
    46
    0
            Some(utils::parse_type(state)?)
    47
        } else {
    48
    0
            None
    49
        };
    50
    51
        // Parse body
    52
    0
        let body = super::parse_expr_recursive(state)?;
    53
    54
    0
        Ok(Expr::new(
    55
    0
            ExprKind::Function {
    56
    0
                name,
    57
    0
                type_params,
    58
    0
                params,
    59
    0
                return_type,
    60
    0
                body: Box::new(body),
    61
    0
                is_async,
    62
    0
                is_pub,
    63
    0
            },
    64
    0
            start_span,
    65
    0
        ))
    66
    0
    }
    67
    68
    0
    fn parse_lambda_params(state: &mut ParserState) -> Result<Vec<Param>> {
    69
    0
        let mut params = Vec::new();
    70
    71
        // Parse parameters until we hit a pipe or arrow
    72
        loop {
    73
            // Check if we've reached the end of parameters
    74
    0
            if matches!(state.tokens.peek(), Some((Token::Pipe, _))) {
    75
    0
                break;
    76
    0
            }
    77
    78
            // Parse parameter name
    79
    0
            let name = if let Some((Token::Identifier(n), _)) = state.tokens.peek() {
    80
    0
                let name = n.clone();
    81
    0
                state.tokens.advance();
    82
    0
                name
    83
            } else {
    84
    0
                break; // No more parameters
    85
            };
    86
    87
            // Parse optional type annotation
    88
    0
            let ty = if matches!(state.tokens.peek(), Some((Token::Colon, _))) {
    89
    0
                state.tokens.advance(); // consume :
    90
    0
                utils::parse_type(state)?
    91
            } else {
    92
                // Default to inferred type - use _ as placeholder
    93
    0
                Type {
    94
    0
                    kind: TypeKind::Named("_".to_string()),
    95
    0
                    span: Span { start: 0, end: 0 },
    96
    0
                }
    97
            };
    98
    99
    0
            params.push(Param {
    100
    0
                pattern: Pattern::Identifier(name),
    101
    0
                ty,
    102
    0
                span: Span { start: 0, end: 0 },
    103
    0
                is_mutable: false,
    104
    0
                default_value: None,
    105
    0
            });
    106
    107
            // Check for comma
    108
    0
            if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    109
    0
                state.tokens.advance(); // consume comma
    110
    0
            } else {
    111
    0
                break;
    112
            }
    113
        }
    114
    115
    0
        Ok(params)
    116
    0
    }
    117
    118
    /// # Errors
    119
    ///
    120
    /// Returns an error if the operation fails
    121
    /// # Errors
    122
    ///
    123
    /// Returns an error if the operation fails
    124
    0
    pub fn parse_empty_lambda(state: &mut ParserState) -> Result<Expr> {
    125
    0
        let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume ||
    126
    127
        // Lambda syntax: || expr (no => allowed)
    128
    129
        // Parse the body
    130
    0
        let body = super::parse_expr_recursive(state)?;
    131
    132
    0
        Ok(Expr::new(
    133
    0
            ExprKind::Lambda {
    134
    0
                params: Vec::new(),
    135
    0
                body: Box::new(body),
    136
    0
            },
    137
    0
            start_span,
    138
    0
        ))
    139
    0
    }
    140
    141
    /// # Errors
    142
    ///
    143
    /// Returns an error if the operation fails
    144
    /// # Errors
    145
    ///
    146
    /// Returns an error if the operation fails
    147
    0
    pub fn parse_lambda(state: &mut ParserState) -> Result<Expr> {
    148
    0
        let start_span = state
    149
    0
            .tokens
    150
    0
            .peek()
    151
    0
            .map_or(Span { start: 0, end: 0 }, |(_, s)| *s);
    152
    153
        // Check syntax type and parse accordingly
    154
    0
        let params = if matches!(state.tokens.peek(), Some((Token::Backslash, _))) {
    155
    0
            parse_backslash_lambda(state)?
    156
        } else {
    157
    0
            parse_pipe_lambda(state)?
    158
        };
    159
    160
        // Parse body
    161
    0
        let body = super::parse_expr_recursive(state)?;
    162
    163
    0
        Ok(Expr::new(
    164
    0
            ExprKind::Lambda {
    165
    0
                params,
    166
    0
                body: Box::new(body),
    167
    0
            },
    168
    0
            start_span,
    169
    0
        ))
    170
    0
    }
    171
    172
    /// Parse backslash-style lambda: \x, y -> body (complexity: 6)
    173
    0
    fn parse_backslash_lambda(state: &mut ParserState) -> Result<Vec<Param>> {
    174
    0
        state.tokens.advance(); // consume \
    175
    176
    0
        let params = parse_simple_params(state)?;
    177
    178
        // Expect arrow
    179
    0
        state.tokens.expect(&Token::Arrow)?;
    180
    181
    0
        Ok(params)
    182
    0
    }
    183
    184
    /// Parse pipe-style lambda: |x, y| body (complexity: 5)
    185
    0
    fn parse_pipe_lambda(state: &mut ParserState) -> Result<Vec<Param>> {
    186
    0
        state.tokens.advance(); // consume |
    187
    188
        // Handle || as empty params
    189
    0
        if matches!(state.tokens.peek(), Some((Token::Pipe, _))) {
    190
    0
            state.tokens.advance(); // consume second |
    191
    0
            return Ok(Vec::new());
    192
    0
        }
    193
    194
        // Parse parameters between pipes
    195
    0
        let params = parse_lambda_params(state)?;
    196
    197
        // Expect closing pipe
    198
    0
        if !matches!(state.tokens.peek(), Some((Token::Pipe, _))) {
    199
    0
            bail!("Expected '|' after lambda parameters");
    200
    0
        }
    201
    0
        state.tokens.advance(); // consume |
    202
    203
    0
        Ok(params)
    204
    0
    }
    205
    206
    /// Parse simple comma-separated parameters (complexity: 6)
    207
    0
    fn parse_simple_params(state: &mut ParserState) -> Result<Vec<Param>> {
    208
    0
        let mut params = Vec::new();
    209
    210
        // Parse first parameter if present
    211
    0
        if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
    212
    0
            params.push(create_simple_param(name.clone()));
    213
    0
            state.tokens.advance();
    214
    215
            // Parse additional parameters
    216
    0
            while matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    217
    0
                state.tokens.advance(); // consume comma
    218
    0
                if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
    219
    0
                    params.push(create_simple_param(name.clone()));
    220
    0
                    state.tokens.advance();
    221
    0
                }
    222
            }
    223
    0
        }
    224
    225
    0
        Ok(params)
    226
    0
    }
    227
    228
    /// Create a simple parameter with default type (complexity: 1)
    229
    0
    fn create_simple_param(name: String) -> Param {
    230
    0
        Param {
    231
    0
            pattern: Pattern::Identifier(name),
    232
    0
            ty: Type {
    233
    0
                kind: TypeKind::Named("Any".to_string()),
    234
    0
                span: Span { start: 0, end: 0 },
    235
    0
            },
    236
    0
            span: Span { start: 0, end: 0 },
    237
    0
            is_mutable: false,
    238
    0
            default_value: None,
    239
    0
        }
    240
    0
    }
    241
    242
    243
    /// # Errors
    244
    ///
    245
    /// Returns an error if the operation fails
    246
    /// # Errors
    247
    ///
    248
    /// Returns an error if the operation fails
    249
    0
    pub fn parse_call(state: &mut ParserState, func: Expr) -> Result<Expr> {
    250
    0
        state.tokens.advance(); // consume (
    251
    252
    0
        let mut args = Vec::new();
    253
    0
        while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
    254
    0
            args.push(super::parse_expr_recursive(state)?);
    255
    256
    0
            if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    257
    0
                state.tokens.advance(); // consume comma
    258
    0
            } else {
    259
    0
                break;
    260
            }
    261
        }
    262
    263
    0
        state.tokens.expect(&Token::RightParen)?;
    264
    265
    0
        Ok(Expr {
    266
    0
            kind: ExprKind::Call {
    267
    0
                func: Box::new(func),
    268
    0
                args,
    269
    0
            },
    270
    0
            span: Span { start: 0, end: 0 },
    271
    0
            attributes: Vec::new(),
    272
    0
        })
    273
    0
    }
    274
    275
    /// # Errors
    276
    ///
    277
    /// Returns an error if the operation fails
    278
    /// # Errors
    279
    ///
    280
    /// Returns an error if the operation fails
    281
    #[allow(clippy::too_many_lines)]
    282
    0
    pub fn parse_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> {
    283
        // Check for special postfix operators like .await
    284
    0
        if let Some((Token::Await, _)) = state.tokens.peek() {
    285
    0
            state.tokens.advance(); // consume await
    286
    0
            return Ok(Expr {
    287
    0
                kind: ExprKind::Await {
    288
    0
                    expr: Box::new(receiver),
    289
    0
                },
    290
    0
                span: Span { start: 0, end: 0 },
    291
    0
                attributes: Vec::new(),
    292
    0
            });
    293
    0
        }
    294
    295
        // Parse method name or tuple index
    296
    0
        match state.tokens.peek() {
    297
    0
            Some((Token::Identifier(name), _)) => {
    298
    0
                let method = name.clone();
    299
    0
                state.tokens.advance();
    300
    0
                parse_method_or_field_access(state, receiver, method)
    301
            }
    302
    0
            Some((Token::Integer(index), _)) => {
    303
                // Handle tuple access like t.0, t.1, etc.
    304
    0
                let index = *index;
    305
    0
                state.tokens.advance();
    306
    0
                Ok(Expr {
    307
    0
                    kind: ExprKind::FieldAccess {
    308
    0
                        object: Box::new(receiver),
    309
    0
                        field: index.to_string(),
    310
    0
                    },
    311
    0
                    span: Span { start: 0, end: 0 },
    312
    0
                    attributes: Vec::new(),
    313
    0
                })
    314
            }
    315
            _ => {
    316
    0
                bail!("Expected method name, tuple index, or 'await' after '.'");
    317
            }
    318
        }
    319
    0
    }
    320
    321
    0
    pub fn parse_optional_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> {
    322
        // Parse method name or tuple index for optional chaining
    323
    0
        match state.tokens.peek() {
    324
    0
            Some((Token::Identifier(name), _)) => {
    325
    0
                let method = name.clone();
    326
    0
                state.tokens.advance();
    327
    0
                parse_optional_method_or_field_access(state, receiver, method)
    328
            }
    329
    0
            Some((Token::Integer(index), _)) => {
    330
                // Handle optional tuple access like t?.0, t?.1, etc.
    331
    0
                let index = *index;
    332
    0
                state.tokens.advance();
    333
    0
                Ok(Expr {
    334
    0
                    kind: ExprKind::OptionalFieldAccess {
    335
    0
                        object: Box::new(receiver),
    336
    0
                        field: index.to_string(),
    337
    0
                    },
    338
    0
                    span: Span { start: 0, end: 0 },
    339
    0
                    attributes: Vec::new(),
    340
    0
                })
    341
            }
    342
            _ => {
    343
    0
                bail!("Expected method name or tuple index after '?.'");
    344
            }
    345
        }
    346
    0
    }
    347
    348
    0
    fn parse_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> {
    349
        // Check if it's a method call (with parentheses) or field access
    350
    0
        if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) {
    351
    0
            parse_method_call_access(state, receiver, method)
    352
        } else {
    353
            // Field access
    354
    0
            Ok(create_field_access(receiver, method))
    355
        }
    356
    0
    }
    357
    358
    /// Parse method call with arguments (complexity: 6)
    359
    0
    fn parse_method_call_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> {
    360
    0
        state.tokens.advance(); // consume (
    361
    0
        let args = parse_method_arguments(state)?;
    362
    0
        state.tokens.expect(&Token::RightParen)?;
    363
    364
        // Check if this is a DataFrame operation
    365
    0
        if is_dataframe_method(&method) {
    366
    0
            handle_dataframe_method(receiver, method, args)
    367
        } else {
    368
    0
            Ok(create_method_call(receiver, method, args))
    369
        }
    370
    0
    }
    371
    372
    /// Parse method arguments (complexity: 5)
    373
    0
    fn parse_method_arguments(state: &mut ParserState) -> Result<Vec<Expr>> {
    374
    0
        let mut args = Vec::new();
    375
    0
        while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
    376
    0
            args.push(super::parse_expr_recursive(state)?);
    377
    378
    0
            if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    379
    0
                state.tokens.advance(); // consume comma
    380
    0
            } else {
    381
    0
                break;
    382
            }
    383
        }
    384
    0
        Ok(args)
    385
    0
    }
    386
    387
    /// Check if method is DataFrame-specific (complexity: 1)
    388
    0
    fn is_dataframe_method(method: &str) -> bool {
    389
    0
        matches!(
    390
    0
            method,
    391
    0
            "select" | "groupby" | "group_by" | "agg" | "pivot" | "melt" |
    392
    0
            "join" | "rolling" | "shift" | "diff" | "pct_change" | "corr" | "cov"
    393
        )
    394
    0
    }
    395
    396
    /// Handle DataFrame-specific methods (complexity: 4)
    397
    0
    fn handle_dataframe_method(receiver: Expr, method: String, args: Vec<Expr>) -> Result<Expr> {
    398
    0
        let operation = match method.as_str() {
    399
    0
            "select" => DataFrameOp::Select(extract_select_columns(args)),
    400
    0
            "groupby" | "group_by" => DataFrameOp::GroupBy(extract_groupby_columns(args)),
    401
    0
            _ => return Ok(create_method_call(receiver, method, args)),
    402
        };
    403
    404
    0
        Ok(Expr {
    405
    0
            kind: ExprKind::DataFrameOperation {
    406
    0
                source: Box::new(receiver),
    407
    0
                operation,
    408
    0
            },
    409
    0
            span: Span { start: 0, end: 0 },
    410
    0
            attributes: Vec::new(),
    411
    0
        })
    412
    0
    }
    413
    414
    /// Extract column names from select arguments (complexity: 8)
    415
    0
    fn extract_select_columns(args: Vec<Expr>) -> Vec<String> {
    416
    0
        let mut columns = Vec::new();
    417
    0
        for arg in args {
    418
    0
            match arg.kind {
    419
                // Handle bare identifiers: .select(age, name)
    420
    0
                ExprKind::Identifier(name) => {
    421
    0
                    columns.push(name);
    422
    0
                }
    423
                // Handle list literals: .select(["age", "name"])
    424
    0
                ExprKind::List(items) => {
    425
    0
                    for item in items {
    426
    0
                        if let ExprKind::Literal(Literal::String(col_name)) = item.kind {
    427
    0
                            columns.push(col_name);
    428
    0
                        }
    429
                    }
    430
                }
    431
                // Handle single string literals: .select("age")
    432
    0
                ExprKind::Literal(Literal::String(col_name)) => {
    433
    0
                    columns.push(col_name);
    434
    0
                }
    435
    0
                _ => {}
    436
            }
    437
        }
    438
    0
        columns
    439
    0
    }
    440
    441
    /// Extract column names from groupby arguments (complexity: 3)
    442
    0
    fn extract_groupby_columns(args: Vec<Expr>) -> Vec<String> {
    443
    0
        args.into_iter()
    444
    0
            .filter_map(|arg| {
    445
    0
                if let ExprKind::Identifier(name) = arg.kind {
    446
    0
                    Some(name)
    447
                } else {
    448
    0
                    None
    449
                }
    450
    0
            })
    451
    0
            .collect()
    452
    0
    }
    453
    454
    /// Create a method call expression (complexity: 1)
    455
    0
    fn create_method_call(receiver: Expr, method: String, args: Vec<Expr>) -> Expr {
    456
    0
        Expr {
    457
    0
            kind: ExprKind::MethodCall {
    458
    0
                receiver: Box::new(receiver),
    459
    0
                method,
    460
    0
                args,
    461
    0
            },
    462
    0
            span: Span { start: 0, end: 0 },
    463
    0
            attributes: Vec::new(),
    464
    0
        }
    465
    0
    }
    466
    467
    /// Create a field access expression (complexity: 1)
    468
    0
    fn create_field_access(receiver: Expr, field: String) -> Expr {
    469
    0
        Expr {
    470
    0
            kind: ExprKind::FieldAccess {
    471
    0
                object: Box::new(receiver),
    472
    0
                field,
    473
    0
            },
    474
    0
            span: Span { start: 0, end: 0 },
    475
    0
            attributes: Vec::new(),
    476
    0
        }
    477
    0
    }
    478
    479
    0
    fn parse_optional_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> {
    480
        // Check if it's a method call (with parentheses) or field access
    481
    0
        if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) {
    482
            // Optional method call - convert to OptionalMethodCall AST node
    483
            // For now, we'll just parse as regular method call but with optional semantics
    484
    0
            state.tokens.advance(); // consume (
    485
    486
    0
            let mut args = Vec::new();
    487
    0
            while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
    488
    0
                args.push(super::parse_expr_recursive(state)?);
    489
    490
    0
                if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    491
    0
                    state.tokens.advance(); // consume comma
    492
    0
                } else {
    493
    0
                    break;
    494
                }
    495
            }
    496
    497
    0
            state.tokens.expect(&Token::RightParen)?;
    498
    499
            // Create an OptionalMethodCall expression
    500
    0
            Ok(Expr {
    501
    0
                kind: ExprKind::OptionalMethodCall {
    502
    0
                    receiver: Box::new(receiver),
    503
    0
                    method,
    504
    0
                    args,
    505
    0
                },
    506
    0
                span: Span { start: 0, end: 0 },
    507
    0
                attributes: Vec::new(),
    508
    0
            })
    509
        } else {
    510
            // Optional field access
    511
    0
            Ok(Expr {
    512
    0
                kind: ExprKind::OptionalFieldAccess {
    513
    0
                    object: Box::new(receiver),
    514
    0
                    field: method,
    515
    0
                },
    516
    0
                span: Span { start: 0, end: 0 },
    517
    0
                attributes: Vec::new(),
    518
    0
            })
    519
        }
    520
    0
    }