perf(parser): use peek_token instead of lookahead on the modifier path (#22842)

## What

The modifier-disambiguation helpers (`get_modifier`, `next_token_can_follow_modifier`, `next_token_is_on_same_line_and_can_follow_modifier`) used the parser-level `lookahead`, which performs a full `checkpoint` — lexer checkpoint **plus** saving `cur_token`, `prev_token_end`, `errors.len()` and taking `fatal_error` — then a `bump_any`, then a `rewind`, just to inspect the single token after a modifier keyword.

This replaces them with the lexer-level `peek_token`, which does the same single re-lex without the parser-field save/restore.

- `can_follow_modifier` / `can_follow_get_or_set_keyword` now take a `Kind`, so the peeked token's kind is passed directly (no duplicated helpers to keep in sync).
- `get_modifier_worker` is removed, folded into `get_modifier`.

Net result is **less code** as well as fewer instructions per modifier check.

## Correctness

Semantics-preserving:

- The speculative `bump_any` in the old form could push an escaped-keyword error, but `lookahead`'s rewind always discarded it; the real error still fires on the non-speculative bump the caller performs once the check returns `true`.
- The `is_on_new_line` flag read from the peeked token is identical to the post-bump `cur_token().is_on_new_line()` the old code read — both refer to the same lexed token (`peek_token` = checkpoint + `next_token` + rewind, and the lexer sets the newline flag while lexing that token).

Verified:
- `cargo test -p oxc_parser`: 69/69 pass
- `cargo coverage -- parser`: zero conformance snapshot diff (Test262 / TypeScript / Babel / estree)
- `just fmt` + `cargo clippy -p oxc_parser --all-features --all-targets`: clean

## Performance

Measured via an interleaved A/B (pre-built binaries, cooldowns) validated against a parse-invariant control (`estree/checker.ts`, which times serialization only and stays flat). The changed build was faster in every measured round on declaration/member-dense input — conservatively **~0.5–1% on `binder.ts`** — and noise-level on type-dominated files (`cal.com.tsx`, `kitchen-sink.tsx`), as expected since those are dominated by TS type parsing rather than modifier scanning.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Boshen committed 2026-05-29 17:35:10 +00:00
1 parent 1e62653d55
commit 9e741c23f7
1 file changed
+40 -46
+40 -46
View File
@@ -500,27 +500,24 @@ impl<C: Config> ParserImpl<'_, C> {
}
fn get_modifier(&mut self) -> Option<ModifierKind> {
let modifier_kind = ModifierKind::try_from(self.cur_kind()).ok()?;
if self.lookahead(Self::get_modifier_worker) { Some(modifier_kind) } else { None }
}
fn get_modifier_worker(&mut self) -> bool {
match self.cur_kind() {
Kind::Const => {
self.bump_any();
self.at(Kind::Enum)
}
let cur_kind = self.cur_kind();
let modifier_kind = ModifierKind::try_from(cur_kind).ok()?;
// Peek the token after the modifier keyword to decide whether the keyword really is a
// modifier (e.g. distinguish `static x` from `static` used as an identifier).
// `peek_token` performs a single lexer-level re-lex, avoiding the heavier parser-level
// `lookahead` (checkpoint + `bump_any` + rewind) that this previously used.
let next = self.lexer.peek_token();
let next_kind = next.kind();
let is_modifier = match cur_kind {
Kind::Const => next_kind == Kind::Enum,
// These modifiers can cross line.
Kind::Accessor | Kind::Static | Kind::Get | Kind::Set => {
// These modifiers can cross line.
self.bump_any();
self.can_follow_modifier()
Self::can_follow_modifier(next_kind)
}
// Rest modifiers cannot cross line
_ => {
self.bump_any();
self.can_follow_modifier() && !self.cur_token().is_on_new_line()
}
}
_ => Self::can_follow_modifier(next_kind) && !next.is_on_new_line(),
};
if is_modifier { Some(modifier_kind) } else { None }
}
fn modifier(&mut self, kind: Kind, span_start: u32) -> Modifier {
@@ -567,7 +564,7 @@ impl<C: Config> ParserImpl<'_, C> {
// We need to ensure that any subsequent modifiers appear on the same line
// so that when 'const' is a standalone declaration, we don't issue
// an error.
if !self.lookahead(Self::next_token_is_on_same_line_and_can_follow_modifier) {
if !self.next_token_is_on_same_line_and_can_follow_modifier() {
return None;
}
self.bump_any();
@@ -587,7 +584,7 @@ impl<C: Config> ParserImpl<'_, C> {
}
pub(crate) fn parse_contextual_modifier(&mut self, kind: Kind) -> bool {
if self.at(kind) && self.lookahead(Self::next_token_can_follow_modifier) {
if self.at(kind) && self.next_token_can_follow_modifier() {
self.bump_any();
true
} else {
@@ -596,9 +593,7 @@ impl<C: Config> ParserImpl<'_, C> {
}
fn parse_any_contextual_modifier(&mut self) -> bool {
if self.cur_kind().is_modifier_kind()
&& self.lookahead(Self::next_token_can_follow_modifier)
{
if self.cur_kind().is_modifier_kind() && self.next_token_can_follow_modifier() {
self.bump_any();
true
} else {
@@ -606,41 +601,40 @@ impl<C: Config> ParserImpl<'_, C> {
}
}
/// Returns `true` if the token following the current (modifier) token can follow a modifier,
/// i.e. the current token really is a modifier rather than an identifier/expression.
///
/// Inspects the single peeked next token via `peek_token` (one lexer-level re-lex) instead of
/// the heavier parser-level `lookahead` (checkpoint + `bump_any` + rewind). The result is
/// identical: the speculative `bump_any` in the old form could push an escaped-keyword error,
/// but `lookahead`'s rewind always discarded it, and the real error still fires on the
/// non-speculative bump performed by the caller once this returns `true`.
pub(crate) fn next_token_can_follow_modifier(&mut self) -> bool {
match self.cur_kind() {
Kind::Const => {
self.bump_any();
self.at(Kind::Enum)
}
Kind::Static => {
self.bump_any();
self.can_follow_modifier()
}
Kind::Get | Kind::Set => {
self.bump_any();
self.can_follow_get_or_set_keyword()
}
_ => self.next_token_is_on_same_line_and_can_follow_modifier(),
let cur_kind = self.cur_kind();
let next = self.lexer.peek_token();
let next_kind = next.kind();
match cur_kind {
Kind::Const => next_kind == Kind::Enum,
Kind::Static => Self::can_follow_modifier(next_kind),
Kind::Get | Kind::Set => Self::can_follow_get_or_set_keyword(next_kind),
// Other modifiers cannot cross a line.
_ => !next.is_on_new_line() && Self::can_follow_modifier(next_kind),
}
}
fn next_token_is_on_same_line_and_can_follow_modifier(&mut self) -> bool {
self.bump_any();
if self.cur_token().is_on_new_line() {
return false;
}
self.can_follow_modifier()
let next = self.lexer.peek_token();
!next.is_on_new_line() && Self::can_follow_modifier(next.kind())
}
fn can_follow_modifier(&self) -> bool {
match self.cur_kind() {
fn can_follow_modifier(kind: Kind) -> bool {
match kind {
Kind::PrivateIdentifier | Kind::LBrack | Kind::LCurly | Kind::Star | Kind::Dot3 => true,
kind => kind.is_identifier_or_keyword(),
}
}
fn can_follow_get_or_set_keyword(&self) -> bool {
let kind = self.cur_kind();
fn can_follow_get_or_set_keyword(kind: Kind) -> bool {
kind == Kind::LBrack || kind == Kind::PrivateIdentifier || kind.is_literal_property_name()
}
}