From b5d98457e2869c08a3493f6f7c7c067952515254 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Wed, 25 Feb 2026 00:08:20 +0000 Subject: [PATCH] perf(parser): remove const generic param from `finish_next_inner` (#19684) Replace const generic param from `finish_next_inner`. ```diff - fn finish_next_inner(&mut self, kind: Kind) -> Token { + fn finish_next_inner(&mut self, kind: Kind, mode: FinishTokenMode) -> Token { ``` The function is marked `#[inline(always)]` to ensure that `mode` can be statically known from each call site and acts as a const. The perf improvement (5%-8% on `parser_tokens` benchmarks) surprised me. Turns out it's the result of the `#[inline(always)]` (see #19694 which made that change in isolation). Aside from the perf improvement, it's easier to read, and may reduce compile time a bit (as this function is inlined into many call sites). --- crates/oxc_parser/src/lexer/mod.rs | 34 +++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/crates/oxc_parser/src/lexer/mod.rs b/crates/oxc_parser/src/lexer/mod.rs index e50f14e83e515..fcee93d64ee42 100644 --- a/crates/oxc_parser/src/lexer/mod.rs +++ b/crates/oxc_parser/src/lexer/mod.rs @@ -65,6 +65,14 @@ pub enum LexerContext { JsxAttributeValue, } +/// Action to take when finishing a token. +/// Passed to [`Lexer::finish_next_inner`]. +#[derive(Clone, Copy, Eq, PartialEq)] +enum FinishTokenMode { + Push, + Replace, +} + pub struct Lexer<'a, C: Config> { allocator: &'a Allocator, @@ -271,26 +279,32 @@ impl<'a, C: Config> Lexer<'a, C> { #[inline] fn finish_next(&mut self, kind: Kind) -> Token { - self.finish_next_inner::(kind) + self.finish_next_inner(kind, FinishTokenMode::Push) } #[inline] fn finish_next_retokenized(&mut self, kind: Kind) -> Token { - self.finish_next_inner::(kind) + self.finish_next_inner(kind, FinishTokenMode::Replace) } - #[inline] - fn finish_next_inner(&mut self, kind: Kind) -> Token { + // `#[inline(always)]` to ensure is inlined into `finish_next` and `finish_next_retokenized`, + // so that `mode` is statically known + #[expect(clippy::inline_always)] + #[inline(always)] + fn finish_next_inner(&mut self, kind: Kind, mode: FinishTokenMode) -> Token { self.token.set_kind(kind); self.token.set_end(self.offset()); let token = self.token; if self.config.tokens() && !matches!(token.kind(), Kind::Eof | Kind::HashbangComment) { - if REPLACE_SAME_START { - debug_assert!(self.tokens.last().is_some_and(|last| last.start() == token.start())); - let last = self.tokens.last_mut().unwrap(); - *last = token; - } else { - self.tokens.push(token); + match mode { + FinishTokenMode::Push => self.tokens.push(token), + FinishTokenMode::Replace => { + debug_assert!( + self.tokens.last().is_some_and(|last| last.start() == token.start()) + ); + let last = self.tokens.last_mut().unwrap(); + *last = token; + } } } self.trivia_builder.handle_token(token);