Skip to content

Commit b347689

Browse files
committed
add error recovery
1 parent 595b2ae commit b347689

File tree

4 files changed

+4563
-15906
lines changed

4 files changed

+4563
-15906
lines changed

baml_language/crates/baml_compiler_parser/src/parser.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,66 @@ impl<'a> Parser<'a> {
575575
)
576576
}
577577

578+
/// Try to recover from an invalid top-level block like `classs Foo { ... }`.
579+
///
580+
/// Recognizes the pattern: identifier identifier { ... } (where the first identifier
581+
/// looks like a typo for a keyword like class/enum/function).
582+
///
583+
/// Returns true if recovery was performed, false otherwise.
584+
fn try_recover_invalid_block(&mut self) -> bool {
585+
// Check pattern: Word Word LBrace
586+
let is_word = self.at(TokenKind::Word);
587+
let next_is_word = self.peek(1).map(|t| t.kind == TokenKind::Word).unwrap_or(false);
588+
let then_lbrace = self.peek(2).map(|t| t.kind == TokenKind::LBrace).unwrap_or(false);
589+
590+
if !is_word || !next_is_word || !then_lbrace {
591+
return false;
592+
}
593+
594+
// Get the invalid keyword text for the error message
595+
let invalid_keyword = self.current().map(|t| t.text.clone()).unwrap_or_default();
596+
let span = self.current().map(|t| t.span).unwrap_or_default();
597+
598+
// Emit a helpful error message
599+
self.error(
600+
format!(
601+
"Unknown keyword '{}'. Expected 'class', 'enum', 'function', 'client', 'generator', 'test', or 'type'.",
602+
invalid_keyword
603+
),
604+
span,
605+
);
606+
607+
// Wrap the invalid block in an ERROR node
608+
self.start_node(SyntaxKind::ERROR);
609+
610+
// Skip the invalid keyword and name
611+
self.bump(); // invalid keyword (e.g., "classs")
612+
self.bump(); // name (e.g., "WrongClass")
613+
614+
// Skip to matching closing brace
615+
if self.at(TokenKind::LBrace) {
616+
self.bump(); // consume '{'
617+
let mut brace_depth = 1;
618+
619+
while !self.at_end() && brace_depth > 0 {
620+
match self.current().map(|t| t.kind) {
621+
Some(TokenKind::LBrace) => {
622+
brace_depth += 1;
623+
self.bump();
624+
}
625+
Some(TokenKind::RBrace) => {
626+
brace_depth -= 1;
627+
self.bump();
628+
}
629+
_ => self.bump(),
630+
}
631+
}
632+
}
633+
634+
self.finish_node();
635+
true
636+
}
637+
578638
// ============ Consumption ============
579639

580640
/// Consume current token, including all trivia before it (whitespace, newlines, comments).
@@ -3234,6 +3294,9 @@ fn parse_impl(tokens: &[Token], cache: Option<&mut NodeCache>) -> (GreenNode, Ve
32343294
parser.parse_let_stmt();
32353295
} else if parser.at_header_comment_start() {
32363296
parser.consume_header_comment();
3297+
} else if parser.try_recover_invalid_block() {
3298+
// Successfully recovered from invalid block like "classs Foo { ... }"
3299+
// Continue parsing
32373300
} else {
32383301
parser.error_unexpected_token("top-level declaration".to_string());
32393302
parser.bump(); // Skip unknown token

baml_language/crates/baml_ide_tests/test_files/syntax/class/invalid_keyword_in_type_def.baml

Lines changed: 4 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -31,111 +31,21 @@ random_keyword WrongEnum {
3131

3232
//----
3333
//- diagnostics
34-
// Error: Expected top-level declaration, found identifier
34+
// Error: Unknown keyword 'classs'. Expected 'class', 'enum', 'function', 'client', 'generator', 'test', or 'type'.
3535
// ╭─[ class_invalid_keyword_in_type_def.baml:10:1 ]
3636
// │
3737
// 10 │ classs WrongClass {
3838
// │ ───┬──
39-
// │ ╰──── Expected top-level declaration, found identifier
39+
// │ ╰──── Unknown keyword 'classs'. Expected 'class', 'enum', 'function', 'client', 'generator', 'test', or 'type'.
4040
// │
4141
// │ Note: Error code: E0010
4242
// ────╯
43-
// Error: Expected top-level declaration, found identifier
44-
// ╭─[ class_invalid_keyword_in_type_def.baml:10:8 ]
45-
// │
46-
// 10 │ classs WrongClass {
47-
// │ ─────┬────
48-
// │ ╰────── Expected top-level declaration, found identifier
49-
// │
50-
// │ Note: Error code: E0010
51-
// ────╯
52-
// Error: Expected top-level declaration, found '{'
53-
// ╭─[ class_invalid_keyword_in_type_def.baml:10:19 ]
54-
// │
55-
// 10 │ classs WrongClass {
56-
// │ ┬
57-
// │ ╰── Expected top-level declaration, found '{'
58-
// │
59-
// │ Note: Error code: E0010
60-
// ────╯
61-
// Error: Expected top-level declaration, found identifier
62-
// ╭─[ class_invalid_keyword_in_type_def.baml:11:3 ]
63-
// │
64-
// 11 │ field string
65-
// │ ──┬──
66-
// │ ╰──── Expected top-level declaration, found identifier
67-
// │
68-
// │ Note: Error code: E0010
69-
// ────╯
70-
// Error: Expected top-level declaration, found identifier
71-
// ╭─[ class_invalid_keyword_in_type_def.baml:11:9 ]
72-
// │
73-
// 11 │ field string
74-
// │ ───┬──
75-
// │ ╰──── Expected top-level declaration, found identifier
76-
// │
77-
// │ Note: Error code: E0010
78-
// ────╯
79-
// Error: Expected top-level declaration, found '}'
80-
// ╭─[ class_invalid_keyword_in_type_def.baml:12:1 ]
81-
// │
82-
// 12 │ }
83-
// │ ┬
84-
// │ ╰── Expected top-level declaration, found '}'
85-
// │
86-
// │ Note: Error code: E0010
87-
// ────╯
88-
// Error: Expected top-level declaration, found identifier
43+
// Error: Unknown keyword 'random_keyword'. Expected 'class', 'enum', 'function', 'client', 'generator', 'test', or 'type'.
8944
// ╭─[ class_invalid_keyword_in_type_def.baml:14:1 ]
9045
// │
9146
// 14 │ random_keyword WrongEnum {
9247
// │ ───────┬──────
93-
// │ ╰──────── Expected top-level declaration, found identifier
94-
// │
95-
// │ Note: Error code: E0010
96-
// ────╯
97-
// Error: Expected top-level declaration, found identifier
98-
// ╭─[ class_invalid_keyword_in_type_def.baml:14:16 ]
99-
// │
100-
// 14 │ random_keyword WrongEnum {
101-
// │ ────┬────
102-
// │ ╰────── Expected top-level declaration, found identifier
103-
// │
104-
// │ Note: Error code: E0010
105-
// ────╯
106-
// Error: Expected top-level declaration, found '{'
107-
// ╭─[ class_invalid_keyword_in_type_def.baml:14:26 ]
108-
// │
109-
// 14 │ random_keyword WrongEnum {
110-
// │ ┬
111-
// │ ╰── Expected top-level declaration, found '{'
112-
// │
113-
// │ Note: Error code: E0010
114-
// ────╯
115-
// Error: Expected top-level declaration, found identifier
116-
// ╭─[ class_invalid_keyword_in_type_def.baml:15:3 ]
117-
// │
118-
// 15 │ A
119-
// │ ┬
120-
// │ ╰── Expected top-level declaration, found identifier
121-
// │
122-
// │ Note: Error code: E0010
123-
// ────╯
124-
// Error: Expected top-level declaration, found identifier
125-
// ╭─[ class_invalid_keyword_in_type_def.baml:16:3 ]
126-
// │
127-
// 16 │ B
128-
// │ ┬
129-
// │ ╰── Expected top-level declaration, found identifier
130-
// │
131-
// │ Note: Error code: E0010
132-
// ────╯
133-
// Error: Expected top-level declaration, found '}'
134-
// ╭─[ class_invalid_keyword_in_type_def.baml:17:1 ]
135-
// │
136-
// 17 │ }
137-
// │ ┬
138-
// │ ╰── Expected top-level declaration, found '}'
48+
// │ ╰──────── Unknown keyword 'random_keyword'. Expected 'class', 'enum', 'function', 'client', 'generator', 'test', or 'type'.
13949
// │
14050
// │ Note: Error code: E0010
14151
// ────╯

0 commit comments

Comments
 (0)