## 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/error_recovery.rs

    Line
    Count
    Source
    1
    //! Error recovery parser for better error messages and IDE support
    2
    3
    #![allow(clippy::items_after_statements)] // Recovery parser needs constants in local scopes
    4
    5
    use crate::frontend::ast::{
    6
        BinaryOp, Expr, ExprKind, Literal, Param, Pattern, Span, Type, TypeKind,
    7
    };
    8
    use crate::frontend::lexer::{Token, TokenStream};
    9
    use anyhow::Result;
    10
    use std::fmt;
    11
    12
    /// Parse error with recovery information
    13
    #[derive(Debug, Clone)]
    14
    pub struct ParseError {
    15
        pub message: String,
    16
        pub span: Span,
    17
        pub recovery_hint: Option<String>,
    18
        pub expected: Vec<Token>,
    19
        pub found: Option<Token>,
    20
        pub severity: ErrorSeverity,
    21
        pub error_code: ErrorCode,
    22
        pub context: Vec<String>, // Stack of parsing contexts for better error messages
    23
    }
    24
    25
    /// Error severity levels
    26
    #[derive(Debug, Clone, PartialEq)]
    27
    pub enum ErrorSeverity {
    28
        Error,
    29
        Warning,
    30
        Info,
    31
        Hint,
    32
    }
    33
    34
    /// Error codes for categorizing different types of errors
    35
    #[derive(Debug, Clone, PartialEq)]
    36
    pub enum ErrorCode {
    37
        // Syntax errors
    38
        UnexpectedToken,
    39
        MissingToken,
    40
        InvalidSyntax,
    41
    42
        // Type errors
    43
        TypeMismatch,
    44
        UndefinedVariable,
    45
        DuplicateDefinition,
    46
    47
        // Pattern matching errors
    48
        UnreachablePattern,
    49
        NonExhaustivePattern,
    50
    51
        // Import/module errors
    52
        ModuleNotFound,
    53
        SymbolNotFound,
    54
        CircularImport,
    55
    56
        // General errors
    57
        InvalidOperation,
    58
        InternalError,
    59
    }
    60
    61
    impl fmt::Display for ParseError {
    62
    0
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    63
            // Write severity and error code
    64
    0
            write!(f, "[{:?}:{:?}] ", self.severity, self.error_code)?;
    65
    66
            // Write context if available
    67
    0
            if !self.context.is_empty() {
    68
    0
                write!(f, "In {}: ", self.context.join(" -> "))?;
    69
    0
            }
    70
    71
            // Write main message
    72
    0
            write!(f, "{}", self.message)?;
    73
    74
            // Write location info
    75
    0
            write!(
    76
    0
                f,
    77
    0
                " at line {}, column {}",
    78
    0
                self.span.start + 1, // Convert to 1-based indexing for user display
    79
    0
                self.span.end - self.span.start + 1
    80
    0
            )?;
    81
    82
            // Write expected vs found tokens if available
    83
    0
            if !self.expected.is_empty() {
    84
    0
                write!(f, " (expected: {:?}", self.expected)?;
    85
    0
                if let Some(ref found) = self.found {
    86
    0
                    write!(f, ", found: {found:?}")?;
    87
    0
                }
    88
    0
                write!(f, ")")?;
    89
    0
            }
    90
    91
            // Write recovery hint
    92
    0
            if let Some(ref hint) = self.recovery_hint {
    93
    0
                write!(f, "\n  💡 Hint: {hint}")?;
    94
    0
            }
    95
    96
    0
            Ok(())
    97
    0
        }
    98
    }
    99
    100
    impl std::error::Error for ParseError {}
    101
    102
    impl ParseError {
    103
        /// Create a new parse error with basic information
    104
    0
        pub fn new(message: String, span: Span) -> Self {
    105
    0
            Self {
    106
    0
                message,
    107
    0
                span,
    108
    0
                recovery_hint: None,
    109
    0
                expected: Vec::new(),
    110
    0
                found: None,
    111
    0
                severity: ErrorSeverity::Error,
    112
    0
                error_code: ErrorCode::InvalidSyntax,
    113
    0
                context: Vec::new(),
    114
    0
            }
    115
    0
        }
    116
    117
        /// Create an error for unexpected token
    118
    0
        pub fn unexpected_token(expected: Vec<Token>, found: Token, span: Span) -> Self {
    119
    0
            let message = format!("Unexpected token '{found:?}'");
    120
    0
            Self {
    121
    0
                message,
    122
    0
                span,
    123
    0
                recovery_hint: Some(
    124
    0
                    "Check for missing operators, parentheses, or semicolons".to_string(),
    125
    0
                ),
    126
    0
                expected,
    127
    0
                found: Some(found),
    128
    0
                severity: ErrorSeverity::Error,
    129
    0
                error_code: ErrorCode::UnexpectedToken,
    130
    0
                context: Vec::new(),
    131
    0
            }
    132
    0
        }
    133
    134
        /// Create an error for missing token
    135
    0
        pub fn missing_token(expected: Token, span: Span) -> Self {
    136
    0
            let message = format!("Missing '{expected:?}'");
    137
    0
            Self {
    138
    0
                message,
    139
    0
                span,
    140
    0
                recovery_hint: Some(format!("Insert '{expected:?}' here")),
    141
    0
                expected: vec![expected],
    142
    0
                found: None,
    143
    0
                severity: ErrorSeverity::Error,
    144
    0
                error_code: ErrorCode::MissingToken,
    145
    0
                context: Vec::new(),
    146
    0
            }
    147
    0
        }
    148
    149
        /// Add parsing context to the error
    150
        #[must_use]
    151
    0
        pub fn with_context(mut self, context: String) -> Self {
    152
    0
            self.context.push(context);
    153
    0
            self
    154
    0
        }
    155
    156
        /// Add recovery hint
    157
        #[must_use]
    158
    0
        pub fn with_hint(mut self, hint: String) -> Self {
    159
    0
            self.recovery_hint = Some(hint);
    160
    0
            self
    161
    0
        }
    162
    163
        /// Set severity level
    164
        #[must_use]
    165
    0
        pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
    166
    0
            self.severity = severity;
    167
    0
            self
    168
    0
        }
    169
    170
        /// Set error code
    171
        #[must_use]
    172
    0
        pub fn with_code(mut self, code: ErrorCode) -> Self {
    173
    0
            self.error_code = code;
    174
    0
            self
    175
    0
        }
    176
    }
    177
    178
    /// Result of parsing with error recovery
    179
    #[derive(Debug)]
    180
    pub struct ParseResult {
    181
        pub ast: Option<Expr>,
    182
        pub errors: Vec<ParseError>,
    183
        pub partial_ast: bool,
    184
    }
    185
    186
    /// Parser with error recovery capabilities
    187
    pub struct RecoveryParser<'a> {
    188
        tokens: TokenStream<'a>,
    189
        errors: Vec<ParseError>,
    190
        recovery_mode: bool,
    191
        ghost_node_count: usize,
    192
        recursion_depth: usize,
    193
    }
    194
    195
    impl<'a> RecoveryParser<'a> {
    196
        #[must_use]
    197
    0
        pub fn new(input: &'a str) -> Self {
    198
    0
            Self {
    199
    0
                tokens: TokenStream::new(input),
    200
    0
                errors: Vec::new(),
    201
    0
                recovery_mode: false,
    202
    0
                ghost_node_count: 0,
    203
    0
                recursion_depth: 0,
    204
    0
            }
    205
    0
        }
    206
    207
        /// Parse with error recovery, always producing some AST
    208
    0
        pub fn parse_with_recovery(&mut self) -> ParseResult {
    209
    0
            match self.parse_expr_recovery() {
    210
    0
                Ok(ast) => ParseResult {
    211
    0
                    ast: Some(ast),
    212
    0
                    errors: self.errors.clone(),
    213
    0
                    partial_ast: !self.errors.is_empty(),
    214
    0
                },
    215
    0
                Err(_) if !self.errors.is_empty() => {
    216
                    // We have errors but can produce a partial AST
    217
    0
                    let ghost = self.create_ghost_node("Failed to parse expression");
    218
    0
                    ParseResult {
    219
    0
                        ast: Some(ghost),
    220
    0
                        errors: self.errors.clone(),
    221
    0
                        partial_ast: true,
    222
    0
                    }
    223
                }
    224
    0
                Err(e) => {
    225
                    // Fatal error, no recovery possible
    226
    0
                    self.errors.push(
    227
    0
                        ParseError::new(e.to_string(), Span::new(0, 0))
    228
    0
                            .with_code(ErrorCode::InternalError),
    229
                    );
    230
    0
                    ParseResult {
    231
    0
                        ast: None,
    232
    0
                        errors: self.errors.clone(),
    233
    0
                        partial_ast: false,
    234
    0
                    }
    235
                }
    236
            }
    237
    0
        }
    238
    239
    0
        fn parse_expr_recovery(&mut self) -> Result<Expr> {
    240
    0
            self.parse_expr_with_precedence_recovery(0)
    241
    0
        }
    242
    243
    0
        fn parse_expr_with_precedence_recovery(&mut self, min_prec: i32) -> Result<Expr> {
    244
    0
            let mut left = self.parse_prefix_recovery()?;
    245
            const MAX_ITERATIONS: usize = 1000; // Prevent infinite loops
    246
    0
            let mut iteration_count = 0;
    247
    248
            loop {
    249
    0
                iteration_count += 1;
    250
    0
                if iteration_count > MAX_ITERATIONS {
    251
    0
                    self.record_error(
    252
    0
                        "Expression too complex or malformed".to_string(),
    253
    0
                        Some("Simplify the expression".to_string()),
    254
                    );
    255
    0
                    break;
    256
    0
                }
    257
    258
    0
                let Some((token, _)) = self.tokens.peek() else {
    259
    0
                    break;
    260
                };
    261
    262
    0
                if !token.is_binary_op() {
    263
    0
                    break;
    264
    0
                }
    265
    266
    0
                let token_clone = token.clone();
    267
    0
                let prec = Self::precedence(&token_clone);
    268
    0
                if prec < min_prec {
    269
    0
                    break;
    270
    0
                }
    271
    272
    0
                match self.tokens.advance() {
    273
    0
                    Some((op_token, _op_span)) => {
    274
    0
                        let op = match Self::token_to_binary_op(&op_token) {
    275
    0
                            Ok(op) => op,
    276
    0
                            Err(e) => {
    277
    0
                                self.record_error(format!("Invalid operator: {e}"), None);
    278
                                // Skip the invalid operator and continue
    279
    0
                                continue;
    280
                            }
    281
                        };
    282
    283
    0
                        let right = if let Ok(expr) = self.parse_expr_with_precedence_recovery(prec + 1)
    284
                        {
    285
    0
                            expr
    286
                        } else {
    287
                            // Create ghost node for missing right operand
    288
    0
                            let ghost = self.create_ghost_node("Missing right operand");
    289
    0
                            self.record_error(
    290
    0
                                format!("Expected expression after '{op_token:?}'"),
    291
    0
                                Some("Add the right side of the operation".to_string()),
    292
                            );
    293
    0
                            ghost
    294
                        };
    295
    296
    0
                        let span = left.span.merge(right.span);
    297
    0
                        left = Expr::new(
    298
    0
                            ExprKind::Binary {
    299
    0
                                left: Box::new(left),
    300
    0
                                op,
    301
    0
                                right: Box::new(right),
    302
    0
                            },
    303
    0
                            span,
    304
                        );
    305
                    }
    306
    0
                    None => break,
    307
                }
    308
            }
    309
    310
    0
            Ok(left)
    311
    0
        }
    312
    313
    0
        fn parse_prefix_recovery(&mut self) -> Result<Expr> {
    314
            const MAX_RECURSION_DEPTH: usize = 100;
    315
    316
    0
            self.recursion_depth += 1;
    317
    0
            if self.recursion_depth > MAX_RECURSION_DEPTH {
    318
    0
                self.recursion_depth -= 1;
    319
    0
                self.record_error(
    320
    0
                    "Expression too deeply nested".to_string(),
    321
    0
                    Some("Simplify the expression".to_string()),
    322
                );
    323
    0
                return Ok(self.create_ghost_node("Max recursion depth"));
    324
    0
            }
    325
    326
    0
            let result = match self.tokens.peek() {
    327
    0
                Some((Token::Integer(n), span)) => {
    328
    0
                    let n = *n;
    329
    0
                    let span = *span;
    330
    0
                    self.tokens.advance();
    331
    0
                    Ok(Expr::new(ExprKind::Literal(Literal::Integer(n)), span))
    332
                }
    333
    0
                Some((Token::Float(f), span)) => {
    334
    0
                    let f = *f;
    335
    0
                    let span = *span;
    336
    0
                    self.tokens.advance();
    337
    0
                    Ok(Expr::new(ExprKind::Literal(Literal::Float(f)), span))
    338
                }
    339
    0
                Some((Token::String(s) | Token::RawString(s), span)) => {
    340
    0
                    let s = s.clone();
    341
    0
                    let span = *span;
    342
    0
                    self.tokens.advance();
    343
    0
                    Ok(Expr::new(ExprKind::Literal(Literal::String(s)), span))
    344
                }
    345
    0
                Some((Token::Bool(b), span)) => {
    346
    0
                    let b = *b;
    347
    0
                    let span = *span;
    348
    0
                    self.tokens.advance();
    349
    0
                    Ok(Expr::new(ExprKind::Literal(Literal::Bool(b)), span))
    350
                }
    351
    0
                Some((Token::Identifier(name), span)) => {
    352
    0
                    let name = name.clone();
    353
    0
                    let span = *span;
    354
    0
                    self.tokens.advance();
    355
    0
                    Ok(Expr::new(ExprKind::Identifier(name), span))
    356
                }
    357
    0
                Some((Token::If, _)) => Ok(self.parse_if_recovery()),
    358
    0
                Some((Token::Let, _)) => Ok(self.parse_let_recovery()),
    359
    0
                Some((Token::Fun, _)) => Ok(self.parse_function_recovery()),
    360
    0
                Some((Token::LeftBracket, _)) => Ok(self.parse_list_recovery()),
    361
    0
                Some((Token::LeftParen, _)) => self.parse_paren_recovery(),
    362
    0
                Some((token, _span)) => {
    363
    0
                    let token_clone = token.clone();
    364
    365
                    // Special handling for binary operators in prefix position
    366
    0
                    if token.is_binary_op() {
    367
    0
                        self.tokens.advance(); // Consume the misplaced operator
    368
    0
                        self.record_error(
    369
    0
                            format!("Unexpected operator: {token_clone:?}"),
    370
    0
                            Some("An expression was expected here, not an operator".to_string()),
    371
                        );
    372
                        // Try to parse what comes after the misplaced operator
    373
    0
                        return self.parse_prefix_recovery();
    374
    0
                    }
    375
    376
    0
                    self.tokens.advance(); // Advance before recording error
    377
    0
                    self.record_error(
    378
    0
                        format!("Unexpected token: {token_clone:?}"),
    379
    0
                        Some("Expected an expression".to_string()),
    380
                    );
    381
                    // Try to recover
    382
    0
                    self.synchronize();
    383
    0
                    Ok(self.create_ghost_node("Unexpected token"))
    384
                }
    385
                None => {
    386
    0
                    self.record_error(
    387
    0
                        "Unexpected end of input".to_string(),
    388
    0
                        Some("Add more code to complete the expression".to_string()),
    389
                    );
    390
    0
                    Ok(self.create_ghost_node("Unexpected EOF"))
    391
                }
    392
            };
    393
    394
    0
            self.recursion_depth -= 1;
    395
    0
            result
    396
    0
        }
    397
    398
    0
        fn parse_if_recovery(&mut self) -> Expr {
    399
    0
            let start_span = self.expect_or_recover(&Token::If);
    400
    401
    0
            let condition = if let Ok(expr) = self.parse_expr_recovery() {
    402
    0
                Box::new(expr)
    403
            } else {
    404
    0
                self.record_error(
    405
    0
                    "Missing condition in if expression".to_string(),
    406
    0
                    Some("Add a condition after 'if'".to_string()),
    407
                );
    408
    0
                Box::new(self.create_ghost_node("Missing condition"))
    409
            };
    410
    411
    0
            let _ = self.expect_or_recover(&Token::LeftBrace);
    412
    0
            let then_branch = Box::new(self.parse_block_recovery());
    413
    414
    0
            let else_branch = if matches!(self.tokens.peek(), Some((Token::Else, _))) {
    415
    0
                self.tokens.advance();
    416
    0
                let _ = self.expect_or_recover(&Token::LeftBrace);
    417
    0
                Some(Box::new(self.parse_block_recovery()))
    418
            } else {
    419
    0
                None
    420
            };
    421
    422
    0
            let span = if let Some(ref else_br) = else_branch {
    423
    0
                start_span.merge(else_br.span)
    424
            } else {
    425
    0
                start_span.merge(then_branch.span)
    426
            };
    427
    428
    0
            Expr::new(
    429
    0
                ExprKind::If {
    430
    0
                    condition,
    431
    0
                    then_branch,
    432
    0
                    else_branch,
    433
    0
                },
    434
    0
                span,
    435
            )
    436
    0
        }
    437
    438
    0
        fn parse_let_recovery(&mut self) -> Expr {
    439
    0
            let start_span = self.expect_or_recover(&Token::Let);
    440
    441
    0
            let name = if let Some((Token::Identifier(name), _)) = self.tokens.advance() {
    442
    0
                name
    443
            } else {
    444
    0
                self.record_error(
    445
    0
                    "Expected identifier after 'let'".to_string(),
    446
    0
                    Some("Add a variable name".to_string()),
    447
                );
    448
    0
                format!("_ghost_{}", self.ghost_node_count)
    449
            };
    450
    451
    0
            let _ = self.expect_or_recover(&Token::Equal);
    452
    453
    0
            let value = if let Ok(expr) = self.parse_expr_recovery() {
    454
    0
                Box::new(expr)
    455
            } else {
    456
    0
                self.record_error(
    457
    0
                    "Missing value in let binding".to_string(),
    458
    0
                    Some("Add a value after '='".to_string()),
    459
                );
    460
    0
                Box::new(self.create_ghost_node("Missing value"))
    461
            };
    462
    463
    0
            let body = if matches!(self.tokens.peek(), Some((Token::In, _))) {
    464
    0
                self.tokens.advance();
    465
    0
                match self.parse_expr_recovery() {
    466
    0
                    Ok(expr) => Box::new(expr),
    467
    0
                    Err(_) => Box::new(self.create_ghost_node("Missing body")),
    468
                }
    469
            } else {
    470
    0
                Box::new(Expr::new(ExprKind::Literal(Literal::Unit), value.span))
    471
            };
    472
    473
    0
            let span = start_span.merge(body.span);
    474
    0
            Expr::new(
    475
    0
                ExprKind::Let {
    476
    0
                    name,
    477
    0
                    type_annotation: None,
    478
    0
                    value,
    479
    0
                    body,
    480
    0
                    is_mutable: false,
    481
    0
                },
    482
    0
                span,
    483
            )
    484
    0
        }
    485
    486
    0
        fn parse_function_recovery(&mut self) -> Expr {
    487
    0
            let start_span = self.expect_or_recover(&Token::Fun);
    488
    489
    0
            let name = if let Some((Token::Identifier(name), _)) = self.tokens.advance() {
    490
    0
                name
    491
            } else {
    492
    0
                self.record_error(
    493
    0
                    "Expected function name".to_string(),
    494
    0
                    Some("Add a function name after 'fun'".to_string()),
    495
                );
    496
    0
                format!("_ghost_fn_{}", self.ghost_node_count)
    497
            };
    498
    499
    0
            let _ = self.expect_or_recover(&Token::LeftParen);
    500
    0
            let params = self.parse_params_recovery();
    501
    0
            let _ = self.expect_or_recover(&Token::RightParen);
    502
    503
    0
            let return_type = if matches!(self.tokens.peek(), Some((Token::Arrow, _))) {
    504
    0
                self.tokens.advance();
    505
    0
                Some(self.parse_type_recovery())
    506
            } else {
    507
    0
                None
    508
            };
    509
    510
    0
            let _ = self.expect_or_recover(&Token::LeftBrace);
    511
    0
            let body = Box::new(self.parse_block_recovery());
    512
    513
    0
            let span = start_span.merge(body.span);
    514
    0
            Expr::new(
    515
    0
                ExprKind::Function {
    516
    0
                    name,
    517
    0
                    type_params: vec![],
    518
    0
                    params,
    519
    0
                    return_type,
    520
    0
                    body,
    521
    0
                    is_async: false,
    522
    0
                    is_pub: false,
    523
    0
                },
    524
    0
                span,
    525
            )
    526
    0
        }
    527
    528
    0
        fn parse_block_recovery(&mut self) -> Expr {
    529
    0
            let mut exprs = Vec::new();
    530
    0
            let start_span = self
    531
    0
                .tokens
    532
    0
                .peek()
    533
    0
                .map_or(Span::new(0, 0), |(_, span)| *span);
    534
    535
    0
            while !matches!(self.tokens.peek(), Some((Token::RightBrace, _)) | None) {
    536
    0
                if let Ok(expr) = self.parse_expr_recovery() {
    537
    0
                    exprs.push(expr);
    538
    0
                } else {
    539
                    // Try to recover by finding next statement
    540
    0
                    self.synchronize();
    541
    0
                    if self.recovery_mode {
    542
    0
                        exprs.push(self.create_ghost_node("Recovery statement"));
    543
    0
                    }
    544
                }
    545
    546
                // Optional semicolon
    547
    0
                if matches!(self.tokens.peek(), Some((Token::Semicolon, _))) {
    548
    0
                    self.tokens.advance();
    549
    0
                }
    550
            }
    551
    552
    0
            let _ = self.expect_or_recover(&Token::RightBrace);
    553
    554
    0
            let span = if let Some(last) = exprs.last() {
    555
    0
                start_span.merge(last.span)
    556
            } else {
    557
    0
                start_span
    558
            };
    559
    560
    0
            Expr::new(ExprKind::Block(exprs), span)
    561
    0
        }
    562
    563
    0
        fn parse_list_recovery(&mut self) -> Expr {
    564
    0
            let start_span = self.expect_or_recover(&Token::LeftBracket);
    565
    0
            let mut elements = Vec::new();
    566
    567
    0
            while !matches!(self.tokens.peek(), Some((Token::RightBracket, _))) {
    568
    0
                match self.parse_expr_recovery() {
    569
    0
                    Ok(expr) => elements.push(expr),
    570
    0
                    Err(_) => {
    571
    0
                        self.synchronize_to(&[Token::Comma, Token::RightBracket]);
    572
    0
                    }
    573
                }
    574
    575
    0
                if matches!(self.tokens.peek(), Some((Token::Comma, _))) {
    576
    0
                    self.tokens.advance();
    577
    0
                } else {
    578
    0
                    break;
    579
                }
    580
            }
    581
    582
    0
            let end_span = self.expect_or_recover(&Token::RightBracket);
    583
    0
            let span = start_span.merge(end_span);
    584
    585
    0
            Expr::new(ExprKind::List(elements), span)
    586
    0
        }
    587
    588
    0
        fn parse_paren_recovery(&mut self) -> Result<Expr> {
    589
    0
            self.tokens.advance(); // consume (
    590
    0
            let expr = self.parse_expr_recovery()?;
    591
    0
            let _ = self.expect_or_recover(&Token::RightParen);
    592
    0
            Ok(expr)
    593
    0
        }
    594
    595
    0
        fn parse_params_recovery(&mut self) -> Vec<Param> {
    596
    0
            let mut params = Vec::new();
    597
    598
    0
            if matches!(self.tokens.peek(), Some((Token::RightParen, _))) {
    599
    0
                return params;
    600
    0
            }
    601
    602
            loop {
    603
    0
                let Some((Token::Identifier(name), name_span)) = self.tokens.advance() else {
    604
    0
                    self.record_error(
    605
    0
                        "Expected parameter name".to_string(),
    606
    0
                        Some("Add a parameter name".to_string()),
    607
                    );
    608
    0
                    self.synchronize_to(&[Token::Comma, Token::RightParen]);
    609
    0
                    continue;
    610
                };
    611
    612
    0
                let ty = if matches!(self.tokens.peek(), Some((Token::Colon, _))) {
    613
    0
                    self.tokens.advance();
    614
    0
                    self.parse_type_recovery()
    615
                } else {
    616
                    // Default to inferred type
    617
    0
                    Type {
    618
    0
                        kind: TypeKind::Named("_".to_string()),
    619
    0
                        span: name_span,
    620
    0
                    }
    621
                };
    622
    623
    0
                params.push(Param {
    624
    0
                    pattern: Pattern::Identifier(name),
    625
    0
                    ty,
    626
    0
                    span: name_span,
    627
    0
                    is_mutable: false,
    628
    0
                    default_value: None,
    629
    0
                });
    630
    631
    0
                match self.tokens.peek() {
    632
    0
                    Some((Token::Comma, _)) => {
    633
    0
                        self.tokens.advance();
    634
    0
                    }
    635
    0
                    Some((Token::RightParen, _)) => break,
    636
                    _ => {
    637
    0
                        self.record_error("Expected ',' or ')' in parameter list".to_string(), None);
    638
    0
                        break;
    639
                    }
    640
                }
    641
            }
    642
    643
    0
            params
    644
    0
        }
    645
    646
    0
        fn parse_type_recovery(&mut self) -> Type {
    647
    0
            let (base_type, span) = if let Some((Token::Identifier(name), span)) = self.tokens.advance()
    648
            {
    649
    0
                (TypeKind::Named(name), span)
    650
            } else {
    651
    0
                self.record_error(
    652
    0
                    "Expected type".to_string(),
    653
    0
                    Some("Add a type annotation".to_string()),
    654
                );
    655
    0
                (TypeKind::Named("_".to_string()), Span::new(0, 0))
    656
            };
    657
    658
    0
            let kind = if matches!(self.tokens.peek(), Some((Token::Question, _))) {
    659
    0
                self.tokens.advance();
    660
    0
                TypeKind::Optional(Box::new(Type {
    661
    0
                    kind: base_type,
    662
    0
                    span,
    663
    0
                }))
    664
            } else {
    665
    0
                base_type
    666
            };
    667
    668
    0
            Type { kind, span }
    669
    0
        }
    670
    671
        /// Create a ghost node for error recovery
    672
    0
        fn create_ghost_node(&mut self, reason: &str) -> Expr {
    673
    0
            self.ghost_node_count += 1;
    674
    0
            Expr::new(
    675
    0
                ExprKind::Identifier(format!(
    676
    0
                    "_ghost_{}_{}",
    677
    0
                    self.ghost_node_count,
    678
    0
                    reason.replace(' ', "_")
    679
    0
                )),
    680
    0
                Span::new(0, 0),
    681
            )
    682
    0
        }
    683
    684
        /// Record an error for later reporting
    685
    0
        fn record_error(&mut self, message: String, hint: Option<String>) {
    686
    0
            let span = self.tokens.peek().map_or(Span::new(0, 0), |(_, s)| *s);
    687
    688
    0
            let mut error = ParseError::new(message, span);
    689
    0
            if let Some(hint) = hint {
    690
    0
                error = error.with_hint(hint);
    691
    0
            }
    692
    0
            if let Some((found_token, _)) = self.tokens.peek() {
    693
    0
                error.found = Some(found_token.clone());
    694
    0
            }
    695
    0
            self.errors.push(error);
    696
    0
        }
    697
    698
        /// Expect a token or record error and try to recover
    699
    0
        fn expect_or_recover(&mut self, expected: &Token) -> Span {
    700
    0
            match self.tokens.peek() {
    701
    0
                Some((token, span)) if token == expected => {
    702
    0
                    let span = *span;
    703
    0
                    self.tokens.advance();
    704
    0
                    span
    705
                }
    706
                _ => {
    707
    0
                    self.record_error(
    708
    0
                        format!("Expected {expected:?}"),
    709
    0
                        Some(format!("Add '{expected:?}' here")),
    710
                    );
    711
    0
                    self.recovery_mode = true;
    712
    0
                    Span::new(0, 0)
    713
                }
    714
            }
    715
    0
        }
    716
    717
        /// Synchronize to a known recovery point
    718
    0
        fn synchronize(&mut self) {
    719
    0
            self.recovery_mode = true;
    720
    721
            // Synchronization tokens - statement boundaries
    722
    0
            let sync_tokens = [
    723
    0
                Token::Semicolon,
    724
    0
                Token::RightBrace,
    725
    0
                Token::Fun,
    726
    0
                Token::Let,
    727
    0
                Token::If,
    728
    0
                Token::For,
    729
    0
                Token::Match,
    730
    0
            ];
    731
    732
    0
            let mut tokens_consumed = 0;
    733
            const MAX_SYNC_TOKENS: usize = 100; // Prevent infinite loops
    734
    735
    0
            while let Some((token, _)) = self.tokens.peek() {
    736
    0
                if tokens_consumed >= MAX_SYNC_TOKENS {
    737
                    // Force exit to prevent infinite loop
    738
    0
                    break;
    739
    0
                }
    740
    741
    0
                if sync_tokens.iter().any(|t| t == token) {
    742
    0
                    if matches!(token, Token::Semicolon) {
    743
    0
                        self.tokens.advance(); // consume semicolon
    744
    0
                    }
    745
    0
                    break;
    746
    0
                }
    747
    0
                self.tokens.advance();
    748
    0
                tokens_consumed += 1;
    749
            }
    750
    751
    0
            self.recovery_mode = false;
    752
    0
        }
    753
    754
        /// Synchronize to specific tokens
    755
    0
        fn synchronize_to(&mut self, targets: &[Token]) {
    756
    0
            let mut tokens_consumed = 0;
    757
            const MAX_SYNC_TOKENS: usize = 100; // Prevent infinite loops
    758
    759
    0
            while let Some((token, _)) = self.tokens.peek() {
    760
    0
                if tokens_consumed >= MAX_SYNC_TOKENS {
    761
                    // Force exit to prevent infinite loop
    762
    0
                    break;
    763
    0
                }
    764
    765
    0
                if targets.iter().any(|t| t == token) {
    766
    0
                    break;
    767
    0
                }
    768
    0
                self.tokens.advance();
    769
    0
                tokens_consumed += 1;
    770
            }
    771
    0
        }
    772
    773
    0
        fn precedence(token: &Token) -> i32 {
    774
    0
            match token {
    775
    0
                Token::OrOr => 1,
    776
    0
                Token::AndAnd => 2,
    777
    0
                Token::Pipe => 3,
    778
    0
                Token::Caret => 4,
    779
    0
                Token::Ampersand => 5,
    780
    0
                Token::EqualEqual | Token::NotEqual => 6,
    781
    0
                Token::Less | Token::LessEqual | Token::Greater | Token::GreaterEqual => 7,
    782
    0
                Token::LeftShift => 8,
    783
    0
                Token::Plus | Token::Minus => 9,
    784
    0
                Token::Star | Token::Slash | Token::Percent => 10,
    785
    0
                Token::Power => 11,
    786
    0
                _ => 0,
    787
            }
    788
    0
        }
    789
    790
    0
        fn token_to_binary_op(token: &Token) -> Result<BinaryOp> {
    791
    0
            Ok(match token {
    792
    0
                Token::Plus => BinaryOp::Add,
    793
    0
                Token::Minus => BinaryOp::Subtract,
    794
    0
                Token::Star => BinaryOp::Multiply,
    795
    0
                Token::Slash => BinaryOp::Divide,
    796
    0
                Token::Percent => BinaryOp::Modulo,
    797
    0
                Token::Power => BinaryOp::Power,
    798
    0
                Token::EqualEqual => BinaryOp::Equal,
    799
    0
                Token::NotEqual => BinaryOp::NotEqual,
    800
    0
                Token::Less => BinaryOp::Less,
    801
    0
                Token::LessEqual => BinaryOp::LessEqual,
    802
    0
                Token::Greater => BinaryOp::Greater,
    803
    0
                Token::GreaterEqual => BinaryOp::GreaterEqual,
    804
    0
                Token::AndAnd => BinaryOp::And,
    805
    0
                Token::OrOr => BinaryOp::Or,
    806
    0
                Token::Ampersand => BinaryOp::BitwiseAnd,
    807
    0
                Token::Pipe => BinaryOp::BitwiseOr,
    808
    0
                Token::Caret => BinaryOp::BitwiseXor,
    809
    0
                Token::LeftShift => BinaryOp::LeftShift,
    810
    0
                _ => anyhow::bail!("Not a binary operator: {:?}", token),
    811
            })
    812
    0
        }
    813
    }
    814
    815
    #[cfg(test)]
    816
    #[allow(clippy::unwrap_used)]
    817
    mod tests {
    818
        use super::*;
    819
    820
        #[test]
    821
        fn test_recovery_missing_operand() {
    822
            let mut parser = RecoveryParser::new("1 +");
    823
            let result = parser.parse_with_recovery();
    824
    825
            assert!(result.ast.is_some());
    826
            assert!(!result.errors.is_empty());
    827
            assert!(result.partial_ast);
    828
        }
    829
    830
        #[test]
    831
        fn test_recovery_missing_paren() {
    832
            let mut parser = RecoveryParser::new("(1 + 2");
    833
            let result = parser.parse_with_recovery();
    834
    835
            assert!(result.ast.is_some());
    836
            assert!(!result.errors.is_empty());
    837
        }
    838
    839
        #[test]
    840
        fn test_recovery_invalid_token() {
    841
            let mut parser = RecoveryParser::new("let x = @ + 1");
    842
            let result = parser.parse_with_recovery();
    843
    844
            assert!(result.ast.is_some());
    845
            assert!(!result.errors.is_empty());
    846
        }
    847
    848
        #[test]
    849
        fn test_recovery_incomplete_if() {
    850
            let mut parser = RecoveryParser::new("if x > 0 { print(x)");
    851
            let result = parser.parse_with_recovery();
    852
    853
            assert!(result.ast.is_some());
    854
            assert!(!result.errors.is_empty());
    855
            assert!(result.partial_ast);
    856
        }
    857
    858
        #[test]
    859
        fn test_recovery_missing_function_body() {
    860
            let mut parser = RecoveryParser::new("fun foo(x: i32)");
    861
            let result = parser.parse_with_recovery();
    862
    863
            assert!(result.ast.is_some());
    864
            assert!(!result.errors.is_empty());
    865
        }
    866
    867
        #[test]
    868
        fn test_no_errors_on_valid_code() {
    869
            let mut parser = RecoveryParser::new("1 + 2 * 3");
    870
            let result = parser.parse_with_recovery();
    871
    872
            assert!(result.ast.is_some());
    873
            assert!(result.errors.is_empty());
    874
            assert!(!result.partial_ast);
    875
        }
    876
    }