Skip to content

Commit 8f056ad

Browse files
committed
perf(parser): cache cur_kind() to eliminate redundant calls (#14411)
## Summary Optimization to reduce redundant token kind extraction in hot paths by caching `cur_kind()` results before compound checks. ## Problem - Compound checks like `self.at(X) || self.at(Y)` call `cur_kind()` twice - Pattern `self.at(close) || self.has_fatal_error()` calls `cur_kind()` twice since `has_fatal_error()` internally calls it - Token kind extraction involves bit-packing operations: `((self.0 >> 64) & 0xFF) as u8` ## Solution Cache `cur_kind()` in a local variable before compound checks to eliminate redundant calls. ## Changes - **`cursor.rs:parse_delimited_list()`**: Hot loop parsing arrays, objects, parameters - reduced from 2-3 calls per iteration to 1 - **`arrow.rs:68`**: Cached kind for `LParen`/`LAngle` check - **`module.rs:101`**: Cached kind for `LCurly`/`Star` check - **`types.rs`**: Fixed 3 locations (lines 91, 558, 1188) with double `at()` calls ## Impact Every JavaScript array, object literal, function parameter list, and TypeScript type now extracts the token kind fewer times during parsing. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent 2b6a3b4 commit 8f056ad

File tree

8 files changed

+77
-39
lines changed

8 files changed

+77
-39
lines changed

crates/oxc_parser/src/cursor.rs

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,14 @@ impl<'a> ParserImpl<'a> {
344344
{
345345
self.expect(open);
346346
let mut list = self.ast.vec();
347-
while !self.at(close) && !self.has_fatal_error() {
347+
loop {
348+
let kind = self.cur_kind();
349+
if kind == close
350+
|| matches!(kind, Kind::Eof | Kind::Undetermined)
351+
|| self.fatal_error.is_some()
352+
{
353+
break;
354+
}
348355
list.push(f(self));
349356
}
350357
self.expect(close);
@@ -386,16 +393,25 @@ impl<'a> ParserImpl<'a> {
386393
F: Fn(&mut Self) -> T,
387394
{
388395
let mut list = self.ast.vec();
389-
if self.at(close) || self.has_fatal_error() {
396+
// Cache cur_kind() to avoid redundant calls in compound checks
397+
let kind = self.cur_kind();
398+
if kind == close
399+
|| matches!(kind, Kind::Eof | Kind::Undetermined)
400+
|| self.fatal_error.is_some()
401+
{
390402
return (list, None);
391403
}
392404
list.push(f(self));
393405
loop {
394-
if self.at(close) || self.has_fatal_error() {
406+
let kind = self.cur_kind();
407+
if kind == close
408+
|| matches!(kind, Kind::Eof | Kind::Undetermined)
409+
|| self.fatal_error.is_some()
410+
{
395411
return (list, None);
396412
}
397413
self.expect(separator);
398-
if self.at(close) {
414+
if self.cur_kind() == close {
399415
let trailing_separator = self.prev_token_end - 1;
400416
return (list, Some(trailing_separator));
401417
}
@@ -417,25 +433,27 @@ impl<'a> ParserImpl<'a> {
417433
let mut rest: Option<BindingRestElement<'a>> = None;
418434
let mut first = true;
419435
loop {
420-
if self.at(close) || self.has_fatal_error() {
436+
let kind = self.cur_kind();
437+
if kind == close
438+
|| matches!(kind, Kind::Eof | Kind::Undetermined)
439+
|| self.fatal_error.is_some()
440+
{
421441
break;
422442
}
423443

424444
if first {
425445
first = false;
426446
} else {
427447
let comma_span = self.cur_token().span();
428-
if !self.at(Kind::Comma) {
429-
let error = diagnostics::expect_token(
430-
Kind::Comma.to_str(),
431-
self.cur_kind().to_str(),
432-
comma_span,
433-
);
448+
if kind != Kind::Comma {
449+
let error =
450+
diagnostics::expect_token(Kind::Comma.to_str(), kind.to_str(), comma_span);
434451
self.set_fatal_error(error);
435452
break;
436453
}
437454
self.bump_any();
438-
if self.at(close) {
455+
let kind = self.cur_kind();
456+
if kind == close {
439457
if rest.is_some() && !self.ctx.has_ambient() {
440458
self.error(diagnostics::rest_element_trailing_comma(comma_span));
441459
}
@@ -448,7 +466,9 @@ impl<'a> ParserImpl<'a> {
448466
break;
449467
}
450468

451-
if self.at(Kind::Dot3) {
469+
// Re-capture kind to get the current token (may have changed after else branch)
470+
let kind = self.cur_kind();
471+
if kind == Kind::Dot3 {
452472
rest.replace(self.parse_rest_element());
453473
} else {
454474
list.push(parse_element(self));

crates/oxc_parser/src/js/arrow.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ impl<'a> ParserImpl<'a> {
6565
if self.cur_token().is_on_new_line() {
6666
return Tristate::False;
6767
}
68-
if !self.at(Kind::LParen) && !self.at(Kind::LAngle) {
68+
let kind = self.cur_kind();
69+
if kind != Kind::LParen && kind != Kind::LAngle {
6970
return Tristate::False;
7071
}
7172
}

crates/oxc_parser/src/js/module.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,13 @@ impl<'a> ParserImpl<'a> {
9898
self.error(diagnostics::escaped_keyword(token_after_import.span()));
9999
}
100100

101-
if self.at(Kind::LCurly) || self.at(Kind::Star) {
101+
let kind = self.cur_kind();
102+
if kind == Kind::LCurly || kind == Kind::Star {
102103
// `import type { ...`
103104
// `import type * ...`
104105
import_kind = ImportOrExportKind::Type;
105106
has_default_specifier = false;
106-
} else if self.cur_kind().is_binding_identifier() {
107+
} else if kind.is_binding_identifier() {
107108
// `import type something ...`
108109
let token = self.cur_token();
109110
let identifier_after_type = self.parse_binding_identifier();

crates/oxc_parser/src/js/statement.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -620,9 +620,15 @@ impl<'a> ParserImpl<'a> {
620620
};
621621
self.expect(Kind::Colon);
622622
let mut consequent = self.ast.vec();
623-
while !matches!(self.cur_kind(), Kind::Case | Kind::Default | Kind::RCurly)
624-
&& !self.has_fatal_error()
625-
{
623+
loop {
624+
let kind = self.cur_kind();
625+
if matches!(
626+
kind,
627+
Kind::Case | Kind::Default | Kind::RCurly | Kind::Eof | Kind::Undetermined
628+
) || self.fatal_error.is_some()
629+
{
630+
break;
631+
}
626632
let stmt = self.parse_statement_list_item(StatementContext::StatementList);
627633
consequent.push(stmt);
628634
}

crates/oxc_parser/src/jsx/mod.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -241,16 +241,17 @@ impl<'a> ParserImpl<'a> {
241241
let span = self.start_span();
242242
let checkpoint = self.checkpoint();
243243
self.bump_any(); // bump `<`
244+
let kind = self.cur_kind();
244245
// <> open fragment
245-
if self.at(Kind::RAngle) {
246+
if kind == Kind::RAngle {
246247
return Some(JSXChild::Fragment(self.parse_jsx_fragment(span, true)));
247248
}
248249
// <ident open element
249-
if self.at(Kind::Ident) || self.cur_kind().is_any_keyword() {
250+
if kind == Kind::Ident || kind.is_any_keyword() {
250251
return Some(JSXChild::Element(self.parse_jsx_element(span, true)));
251252
}
252253
// </ close fragment
253-
if self.at(Kind::Slash) {
254+
if kind == Kind::Slash {
254255
self.rewind(checkpoint);
255256
return None;
256257
}
@@ -318,10 +319,14 @@ impl<'a> ParserImpl<'a> {
318319
/// `JSXAttribute` `JSXAttributes_opt`
319320
fn parse_jsx_attributes(&mut self) -> Vec<'a, JSXAttributeItem<'a>> {
320321
let mut attributes = self.ast.vec();
321-
while !matches!(self.cur_kind(), Kind::LAngle | Kind::RAngle | Kind::Slash)
322-
&& self.fatal_error.is_none()
323-
{
324-
let attribute = match self.cur_kind() {
322+
loop {
323+
let kind = self.cur_kind();
324+
if matches!(kind, Kind::LAngle | Kind::RAngle | Kind::Slash)
325+
|| self.fatal_error.is_some()
326+
{
327+
break;
328+
}
329+
let attribute = match kind {
325330
Kind::LCurly => {
326331
JSXAttributeItem::SpreadAttribute(self.parse_jsx_spread_attribute())
327332
}
@@ -405,7 +410,8 @@ impl<'a> ParserImpl<'a> {
405410
/// `JSXIdentifier` [no `WhiteSpace` or Comment here] -
406411
fn parse_jsx_identifier(&mut self) -> JSXIdentifier<'a> {
407412
let span = self.start_span();
408-
if !self.at(Kind::Ident) && !self.cur_kind().is_any_keyword() {
413+
let kind = self.cur_kind();
414+
if kind != Kind::Ident && !kind.is_any_keyword() {
409415
return self.unexpected();
410416
}
411417
// Currently at a valid normal Ident or Keyword, keep on lexing for `-` in `<component-name />`

crates/oxc_parser/src/lexer/kind.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,10 @@ impl Kind {
270270
}
271271

272272
/// `IdentifierName`
273+
/// All identifier names are either `Ident` or keywords (Await..=Null in the enum).
273274
#[inline]
274275
pub fn is_identifier_name(self) -> bool {
275-
self == Ident || self.is_any_keyword()
276+
self == Ident || matches!(self as u8, x if x >= Await as u8 && x <= Null as u8)
276277
}
277278

278279
/// Check the succeeding token of a `let` keyword.

crates/oxc_parser/src/modifiers.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ impl<'a> ParserImpl<'a> {
386386
let span = self.start_span();
387387
let kind = self.cur_kind();
388388

389-
if matches!(self.cur_kind(), Kind::Const) {
389+
if kind == Kind::Const {
390390
if !permit_const_as_modifier {
391391
return None;
392392
}
@@ -398,10 +398,10 @@ impl<'a> ParserImpl<'a> {
398398
} else if
399399
// we're at the start of a static block
400400
(stop_on_start_of_class_static_block
401-
&& matches!(self.cur_kind(), Kind::Static)
401+
&& kind == Kind::Static
402402
&& self.lexer.peek_token().kind() == Kind::LCurly)
403403
// we may be at the start of a static block
404-
|| (has_seen_static_modifier && matches!(self.cur_kind(), Kind::Static))
404+
|| (has_seen_static_modifier && kind == Kind::Static)
405405
// next token is not a modifier
406406
|| (!self.parse_any_contextual_modifier())
407407
{

crates/oxc_parser/src/ts/types.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,17 @@ impl<'a> ParserImpl<'a> {
8585
if self.at(Kind::LAngle) {
8686
return true;
8787
}
88-
if self.at(Kind::New) {
88+
let kind = self.cur_kind();
89+
if kind == Kind::New {
8990
return true;
9091
}
91-
if !self.at(Kind::LParen) && !self.at(Kind::Abstract) {
92+
if kind != Kind::LParen && kind != Kind::Abstract {
9293
return false;
9394
}
9495
let checkpoint = self.checkpoint();
95-
let first = self.cur_kind();
9696
self.bump_any();
9797

98-
match first {
98+
match kind {
9999
Kind::Abstract => {
100100
// `abstract new ...`
101101
if self.at(Kind::New) {
@@ -140,11 +140,12 @@ impl<'a> ParserImpl<'a> {
140140
if self.cur_kind().is_modifier_kind() {
141141
self.parse_modifiers(false, false);
142142
}
143-
if self.cur_kind().is_identifier() || self.at(Kind::This) {
143+
let kind = self.cur_kind();
144+
if kind.is_identifier() || kind == Kind::This {
144145
self.bump_any();
145146
return true;
146147
}
147-
if matches!(self.cur_kind(), Kind::LBrack | Kind::LCurly) {
148+
if matches!(kind, Kind::LBrack | Kind::LCurly) {
148149
let errors_count = self.errors_count();
149150
self.parse_binding_pattern_kind();
150151
if !self.has_fatal_error() && errors_count == self.errors_count() {
@@ -555,7 +556,8 @@ impl<'a> ParserImpl<'a> {
555556

556557
fn is_start_of_mapped_type(&mut self) -> bool {
557558
self.bump_any();
558-
if self.at(Kind::Plus) || self.at(Kind::Minus) {
559+
let kind = self.cur_kind();
560+
if kind == Kind::Plus || kind == Kind::Minus {
559561
self.bump_any();
560562
return self.at(Kind::Readonly);
561563
}
@@ -1184,7 +1186,8 @@ impl<'a> ParserImpl<'a> {
11841186
let (key, computed) = self.parse_property_name();
11851187
let optional = self.eat(Kind::Question);
11861188

1187-
if self.at(Kind::LParen) || self.at(Kind::LAngle) {
1189+
let kind = self.cur_kind();
1190+
if kind == Kind::LParen || kind == Kind::LAngle {
11881191
for modifier in modifiers.iter() {
11891192
if modifier.kind == ModifierKind::Readonly {
11901193
self.error(

0 commit comments

Comments
 (0)