Skip to content

Commit e228840

Browse files
committed
feat(parser): fast forward lexer to EOF if errors are encountered (#10579)
This PR prepares for the next optimization: removal of `Result<T>`.
1 parent 8a2b250 commit e228840

File tree

20 files changed

+227
-275
lines changed

20 files changed

+227
-275
lines changed

crates/oxc_linter/src/rules/unicorn/prefer_type_error.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,15 +447,16 @@ fn test() {
447447
// throw new TypeError;
448448
// }
449449
// "#,
450-
r#"
450+
r"
451451
if (typeof foo == 'Foo' || 'Foo' === typeof foo) {
452452
throw new Error();
453453
}
454-
r#"
454+
",
455+
r"
455456
if (Number.isFinite(foo) && Number.isSafeInteger(foo) && Number.isInteger(foo)) {
456457
throw new Error();
457458
}
458-
"#,
459+
",
459460
r"
460461
if (wrapper.n.isFinite(foo) && wrapper.n.isSafeInteger(foo) && wrapper.n.isInteger(foo)) {
461462
throw new Error();

crates/oxc_linter/src/snapshots/unicorn_prefer_type_error.snap

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,23 @@ source: crates/oxc_linter/src/tester.rs
6464
╰────
6565
help: Change to `throw new TypeError(...)`
6666

67-
× Invalid Character `"`
68-
╭─[prefer_type_error.tsx:5:11]
67+
eslint-plugin-unicorn(prefer-type-error): Prefer throwing a `TypeError` over a generic `Error` after a type checking if-statement
68+
╭─[prefer_type_error.tsx:3:27]
69+
2if (typeof foo == 'Foo' || 'Foo' === typeof foo) {
70+
3 │ throw new Error();
71+
· ─────
6972
4 │ }
70-
5r#"
71-
·
72-
6if (Number.isFinite(foo) && Number.isSafeInteger(foo) && Number.isInteger(foo)) {
7373
╰────
74+
help: Change to `throw new TypeError(...)`
7475

75-
× Expected a semicolon or an implicit semicolon after a statement, but found none
76-
╭─[prefer_type_error.tsx:5:10]
76+
eslint-plugin-unicorn(prefer-type-error): Prefer throwing a `TypeError` over a generic `Error` after a type checking if-statement
77+
╭─[prefer_type_error.tsx:3:27]
78+
2if (Number.isFinite(foo) && Number.isSafeInteger(foo) && Number.isInteger(foo)) {
79+
3 │ throw new Error();
80+
· ─────
7781
4 │ }
78-
5r#"
79-
·
80-
6if (Number.isFinite(foo) && Number.isSafeInteger(foo) && Number.isInteger(foo)) {
8182
╰────
82-
help: Try insert a semicolon here
83+
help: Change to `throw new TypeError(...)`
8384

8485
eslint-plugin-unicorn(prefer-type-error): Prefer throwing a `TypeError` over a generic `Error` after a type checking if-statement
8586
╭─[prefer_type_error.tsx:3:27]

crates/oxc_parser/examples/parser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ fn main() -> Result<(), String> {
6060
for error in ret.errors {
6161
let error = error.with_source_code(source_text.clone());
6262
println!("{error:?}");
63-
println!("Parsed with Errors.");
6463
}
64+
println!("Parsed with Errors.");
6565
}
6666

6767
Ok(())

crates/oxc_parser/src/cursor.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ use oxc_span::{GetSpan, Span};
77

88
use crate::{
99
Context, ParserImpl, diagnostics,
10+
error_handler::FatalError,
1011
lexer::{Kind, LexerCheckpoint, LexerContext, Token},
1112
};
1213

13-
#[derive(Clone, Copy)]
14+
#[derive(Clone)]
1415
pub struct ParserCheckpoint<'a> {
1516
lexer: LexerCheckpoint<'a>,
1617
cur_token: Token,
1718
prev_span_end: u32,
1819
errors_pos: usize,
20+
fatal_error: Option<FatalError>,
1921
}
2022

2123
impl<'a> ParserImpl<'a> {
@@ -169,7 +171,8 @@ impl<'a> ParserImpl<'a> {
169171
pub(crate) fn asi(&mut self) -> Result<()> {
170172
if !self.can_insert_semicolon() {
171173
let span = Span::new(self.prev_token_end, self.prev_token_end);
172-
return Err(diagnostics::auto_semicolon_insertion(span));
174+
let error = diagnostics::auto_semicolon_insertion(span);
175+
return Err(self.set_fatal_error(error));
173176
}
174177
if self.at(Kind::Semicolon) {
175178
self.advance(Kind::Semicolon);
@@ -186,10 +189,11 @@ impl<'a> ParserImpl<'a> {
186189
}
187190

188191
/// # Errors
189-
pub(crate) fn expect_without_advance(&self, kind: Kind) -> Result<()> {
192+
pub(crate) fn expect_without_advance(&mut self, kind: Kind) -> Result<()> {
190193
if !self.at(kind) {
191194
let range = self.cur_token().span();
192-
return Err(diagnostics::expect_token(kind.to_str(), self.cur_kind().to_str(), range));
195+
let error = diagnostics::expect_token(kind.to_str(), self.cur_kind().to_str(), range);
196+
return Err(self.set_fatal_error(error));
193197
}
194198
Ok(())
195199
}
@@ -271,22 +275,25 @@ impl<'a> ParserImpl<'a> {
271275
}
272276
}
273277

274-
pub(crate) fn checkpoint(&self) -> ParserCheckpoint<'a> {
278+
pub(crate) fn checkpoint(&mut self) -> ParserCheckpoint<'a> {
275279
ParserCheckpoint {
276280
lexer: self.lexer.checkpoint(),
277281
cur_token: self.token,
278282
prev_span_end: self.prev_token_end,
279283
errors_pos: self.errors.len(),
284+
fatal_error: self.fatal_error.take(),
280285
}
281286
}
282287

283288
pub(crate) fn rewind(&mut self, checkpoint: ParserCheckpoint<'a>) {
284-
let ParserCheckpoint { lexer, cur_token, prev_span_end, errors_pos } = checkpoint;
289+
let ParserCheckpoint { lexer, cur_token, prev_span_end, errors_pos, fatal_error } =
290+
checkpoint;
285291

286292
self.lexer.rewind(lexer);
287293
self.token = cur_token;
288294
self.prev_token_end = prev_span_end;
289295
self.errors.truncate(errors_pos);
296+
self.fatal_error = fatal_error;
290297
}
291298

292299
/// # Errors
@@ -343,7 +350,7 @@ impl<'a> ParserImpl<'a> {
343350
let mut list = self.ast.vec();
344351
loop {
345352
let kind = self.cur_kind();
346-
if kind == close || kind == Kind::Eof {
353+
if kind == close || self.has_fatal_error() {
347354
break;
348355
}
349356
match f(self)? {
@@ -373,7 +380,7 @@ impl<'a> ParserImpl<'a> {
373380
let mut first = true;
374381
loop {
375382
let kind = self.cur_kind();
376-
if kind == close || kind == Kind::Eof {
383+
if kind == close || self.has_fatal_error() {
377384
break;
378385
}
379386
if first {
@@ -408,7 +415,7 @@ impl<'a> ParserImpl<'a> {
408415
let mut first = true;
409416
loop {
410417
let kind = self.cur_kind();
411-
if kind == close || kind == Kind::Eof {
418+
if kind == close || self.has_fatal_error() {
412419
break;
413420
}
414421
if first {

crates/oxc_parser/src/diagnostics.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,11 @@ pub fn a_rest_element_cannot_have_an_initializer(span: Span) -> OxcDiagnostic {
520520
OxcDiagnostic::error("A rest element cannot have an initializer.").with_label(span)
521521
}
522522

523+
#[cold]
524+
pub fn import_requires_a_specifier(span: Span) -> OxcDiagnostic {
525+
OxcDiagnostic::error("import() requires a specifier.").with_label(span)
526+
}
527+
523528
// ================================= MODIFIERS =================================
524529

525530
#[cold]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Code related to error handling.
2+
3+
use oxc_allocator::Dummy;
4+
use oxc_diagnostics::OxcDiagnostic;
5+
6+
use crate::{ParserImpl, diagnostics, lexer::Kind};
7+
8+
/// Fatal parsing error.
9+
#[derive(Debug, Clone)]
10+
pub struct FatalError {
11+
/// The fatal error
12+
pub error: OxcDiagnostic,
13+
/// Length of `errors` at time fatal error is recorded
14+
#[expect(unused)]
15+
pub errors_len: usize,
16+
}
17+
18+
impl<'a> ParserImpl<'a> {
19+
pub(crate) fn set_unexpected(&mut self) -> OxcDiagnostic {
20+
// The lexer should have reported a more meaningful diagnostic
21+
// when it is a undetermined kind.
22+
if matches!(self.cur_kind(), Kind::Eof | Kind::Undetermined) {
23+
if let Some(error) = self.lexer.errors.pop() {
24+
return self.set_fatal_error(error);
25+
}
26+
}
27+
let error = diagnostics::unexpected_token(self.cur_token().span());
28+
self.set_fatal_error(error)
29+
}
30+
31+
/// Return error info at current token
32+
///
33+
/// # Panics
34+
///
35+
/// * The lexer did not push a diagnostic when `Kind::Undetermined` is returned
36+
pub(crate) fn unexpected(&mut self) -> OxcDiagnostic {
37+
self.set_unexpected()
38+
// Dummy::dummy(self.ast.allocator)
39+
}
40+
41+
/// Push a Syntax Error
42+
pub(crate) fn error(&mut self, error: OxcDiagnostic) {
43+
self.errors.push(error);
44+
}
45+
46+
/// Count of all parser and lexer errors.
47+
pub(crate) fn errors_count(&self) -> usize {
48+
self.errors.len() + self.lexer.errors.len()
49+
}
50+
51+
/// Advance lexer's cursor to end of file.
52+
pub(crate) fn set_fatal_error(&mut self, error: OxcDiagnostic) -> OxcDiagnostic {
53+
if self.fatal_error.is_none() {
54+
self.lexer.advance_to_end();
55+
self.fatal_error =
56+
Some(FatalError { error: error.clone(), errors_len: self.errors.len() });
57+
}
58+
error
59+
}
60+
61+
#[expect(unused)]
62+
pub(crate) fn fatal_error<T: Dummy<'a>>(&mut self, error: OxcDiagnostic) -> T {
63+
let _ = self.set_fatal_error(error);
64+
Dummy::dummy(self.ast.allocator)
65+
}
66+
67+
pub(crate) fn has_fatal_error(&self) -> bool {
68+
matches!(self.cur_kind(), Kind::Eof | Kind::Undetermined) || self.fatal_error.is_some()
69+
}
70+
}

crates/oxc_parser/src/js/binding.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ impl<'a> ParserImpl<'a> {
5353
)?;
5454
if let Some(rest) = &rest {
5555
if !matches!(&rest.argument.kind, BindingPatternKind::BindingIdentifier(_)) {
56-
return Err(diagnostics::invalid_binding_rest_element(rest.argument.span()));
56+
let error = diagnostics::invalid_binding_rest_element(rest.argument.span());
57+
return Err(self.set_fatal_error(error));
5758
}
5859
}
5960
self.expect(Kind::RCurly)?;

crates/oxc_parser/src/js/expression.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,13 @@ impl<'a> ParserImpl<'a> {
7373
pub(crate) fn parse_binding_identifier(&mut self) -> Result<BindingIdentifier<'a>> {
7474
let cur = self.cur_kind();
7575
if !cur.is_binding_identifier() {
76-
let err = if cur.is_reserved_keyword() {
77-
diagnostics::identifier_reserved_word(self.cur_token().span(), cur.to_str())
76+
return Err(if cur.is_reserved_keyword() {
77+
let error =
78+
diagnostics::identifier_reserved_word(self.cur_token().span(), cur.to_str());
79+
self.set_fatal_error(error)
7880
} else {
7981
self.unexpected()
80-
};
81-
return Err(err);
82+
});
8283
}
8384
let (span, name) = self.parse_identifier_kind(Kind::Ident);
8485
self.check_identifier(span, &name);
@@ -211,7 +212,8 @@ impl<'a> ParserImpl<'a> {
211212

212213
if expressions.is_empty() {
213214
self.expect(Kind::RParen)?;
214-
return Err(diagnostics::empty_parenthesized_expression(self.end_span(span)));
215+
let error = diagnostics::empty_parenthesized_expression(self.end_span(span));
216+
return Err(self.set_fatal_error(error));
215217
}
216218

217219
let expr_span = self.end_span(expr_span);
@@ -477,14 +479,14 @@ impl<'a> ParserImpl<'a> {
477479
self.re_lex_template_substitution_tail();
478480
loop {
479481
match self.cur_kind() {
480-
Kind::Eof => self.expect(Kind::TemplateTail)?,
481482
Kind::TemplateTail => {
482483
quasis.push(self.parse_template_element(tagged));
483484
break;
484485
}
485486
Kind::TemplateMiddle => {
486487
quasis.push(self.parse_template_element(tagged));
487488
}
489+
_ if self.has_fatal_error() => self.expect(Kind::TemplateTail)?,
488490
_ => {
489491
// TemplateMiddle Expression[+In, ?Yield, ?Await]
490492
let expr =
@@ -737,7 +739,7 @@ impl<'a> ParserImpl<'a> {
737739
| Kind::TemplateHead
738740
| Kind::LBrack => break,
739741
_ => {
740-
return Err(diagnostics::unexpected_token(self.cur_token().span()));
742+
return Err(self.unexpected());
741743
}
742744
}
743745
}
@@ -1050,7 +1052,8 @@ impl<'a> ParserImpl<'a> {
10501052
self.expect(Kind::In)?;
10511053
let right = self.parse_binary_expression_or_higher(Precedence::Lowest)?;
10521054
if let Expression::PrivateInExpression(private_in_expr) = right {
1053-
return Err(diagnostics::private_in_private(private_in_expr.span));
1055+
let error = diagnostics::private_in_private(private_in_expr.span);
1056+
return Err(self.set_fatal_error(error));
10541057
}
10551058
self.ast.expression_private_in(self.end_span(lhs_span), left, right)
10561059
} else {

crates/oxc_parser/src/js/grammar.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ impl<'a> CoverGrammar<'a, ArrayExpression<'a>> for ArrayAssignmentTarget<'a> {
8585
p.error(diagnostics::binding_rest_element_trailing_comma(*span));
8686
}
8787
} else {
88-
return Err(diagnostics::spread_last_element(elem.span));
88+
let error = diagnostics::spread_last_element(elem.span);
89+
return Err(p.set_fatal_error(error));
8990
}
9091
}
9192
ArrayExpressionElement::Elision(_) => elements.push(None),

crates/oxc_parser/src/js/module.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ impl<'a> ParserImpl<'a> {
1717
) -> Result<Expression<'a>> {
1818
self.expect(Kind::LParen)?;
1919
if self.eat(Kind::RParen) {
20-
return Err(oxc_diagnostics::OxcDiagnostic::error("import() requires a specifier.")
21-
.with_label(self.end_span(span)));
20+
let error = diagnostics::import_requires_a_specifier(self.end_span(span));
21+
return Err(self.set_fatal_error(error));
2222
}
2323
let has_in = self.ctx.has_in();
2424
self.ctx = self.ctx.and_in(true);
@@ -31,7 +31,8 @@ impl<'a> ParserImpl<'a> {
3131
// Allow trailing comma
3232
self.bump(Kind::Comma);
3333
if !self.eat(Kind::RParen) {
34-
return Err(diagnostics::import_arguments(self.end_span(span)));
34+
let error = diagnostics::import_arguments(self.end_span(span));
35+
return Err(self.set_fatal_error(error));
3536
}
3637
self.ctx = self.ctx.and_in(has_in);
3738
let expr =

0 commit comments

Comments
 (0)