Skip to content

Commit cbd2a79

Browse files
authored
Improve error recovery in delimited groups when can't parse anything inside (#601)
Simpler variant of #594. While writing an issue for the potential null parse recovery, it just hit me that we might not need to support that generally but rather just limiting that to the delimited group would give us enough bang for the buck. TerminatedBy null parse recovery can be a bit too greedy, i.e. we might recover some unexpected item definition just because... we recovered at the final terminator. However, the delimited group is a reasonable boundary since it's simple (2-3 kinds of general delimiters versus different kinds of potentially versioned terminated statements) and the opening delimiter must always match the closing one, so it produces more reliable and self-contained recovered phrases, hence attempting to recover from a null parse in a delimited group makes more sense.
1 parent 1a258c4 commit cbd2a79

12 files changed

Lines changed: 336 additions & 72 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nomicfoundation/slang": patch
3+
---
4+
5+
Attempt parser error recovery between bracket delimiters

crates/codegen/parser/generator/src/parser_definition.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
187187
|input| Lexer::leading_trivia(self, input),
188188
TokenKind::#close_token,
189189
Self::#delimiters(),
190+
RecoverFromNoMatch::Yes,
190191
)
191192
)?;
192193
},
@@ -256,6 +257,7 @@ impl ParserDefinitionNodeExtensions for ParserDefinitionNode {
256257
|input| Lexer::leading_trivia(self, input),
257258
TokenKind::#terminator_token_kind,
258259
Self::#delimiters(),
260+
RecoverFromNoMatch::No,
259261
)
260262
)?;
261263
},

crates/codegen/parser/runtime/src/support/recovery.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ use crate::text_index::{TextRange, TextRangeExtensions as _};
77
use super::parser_result::SkippedUntil;
88
use super::ParserContext;
99

10+
/// An explicit parameter for the [`ParserResult::recover_until_with_nested_delims`] method.
11+
pub enum RecoverFromNoMatch {
12+
Yes,
13+
No,
14+
}
15+
16+
impl RecoverFromNoMatch {
17+
pub fn as_bool(&self) -> bool {
18+
matches!(self, RecoverFromNoMatch::Yes)
19+
}
20+
}
21+
1022
fn opt_parse(
1123
input: &mut ParserContext,
1224
parse: impl Fn(&mut ParserContext) -> ParserResult,
@@ -34,6 +46,7 @@ impl ParserResult {
3446
leading_trivia: impl Fn(&mut ParserContext) -> ParserResult,
3547
expected: TokenKind,
3648
delims: &[(TokenKind, TokenKind)],
49+
recover_from_no_match: RecoverFromNoMatch,
3750
) -> ParserResult {
3851
let before_recovery = input.position();
3952

@@ -47,10 +60,23 @@ impl ParserResult {
4760
token
4861
};
4962

50-
let (mut nodes, mut expected_tokens, is_incomplete) = match self {
51-
ParserResult::IncompleteMatch(result) => (result.nodes, result.expected_tokens, true),
63+
enum ParseResultKind {
64+
Match,
65+
Incomplete,
66+
NoMatch,
67+
}
68+
69+
let (mut nodes, mut expected_tokens, result_kind) = match self {
70+
ParserResult::IncompleteMatch(result) => (
71+
result.nodes,
72+
result.expected_tokens,
73+
ParseResultKind::Incomplete,
74+
),
5275
ParserResult::Match(result) if peek_token_after_trivia() != Some(expected) => {
53-
(result.nodes, result.expected_tokens, false)
76+
(result.nodes, result.expected_tokens, ParseResultKind::Match)
77+
}
78+
ParserResult::NoMatch(result) if recover_from_no_match.as_bool() => {
79+
(vec![], result.expected_tokens, ParseResultKind::NoMatch)
5480
}
5581
// No need to recover, so just return as-is.
5682
_ => return self,
@@ -61,7 +87,7 @@ impl ParserResult {
6187
match skip_until_with_nested_delims(input, next_token, expected, delims) {
6288
Some((found, skipped_range)) => {
6389
nodes.extend(leading_trivia);
64-
if !is_incomplete {
90+
if matches!(result_kind, ParseResultKind::Match) {
6591
expected_tokens.push(expected);
6692
}
6793

@@ -83,11 +109,13 @@ impl ParserResult {
83109
None => {
84110
input.set_position(before_recovery);
85111

86-
if is_incomplete {
87-
return ParserResult::incomplete_match(nodes, expected_tokens);
88-
} else {
89-
return ParserResult::r#match(nodes, expected_tokens);
90-
}
112+
return match result_kind {
113+
ParseResultKind::Match => ParserResult::r#match(nodes, expected_tokens),
114+
ParseResultKind::Incomplete => {
115+
ParserResult::incomplete_match(nodes, expected_tokens)
116+
}
117+
ParseResultKind::NoMatch => ParserResult::no_match(expected_tokens),
118+
};
91119
}
92120
}
93121
}

0 commit comments

Comments
 (0)