mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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:
1 file changed
+40
-46
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user