From 33705bdc21adc12f3ea8801a4666968581330a4b Mon Sep 17 00:00:00 2001 From: Ulrich Stark Date: Fri, 26 Sep 2025 15:47:23 +0200 Subject: [PATCH 1/2] perf(parser): cleanup and optimize various parsing functions --- crates/oxc_parser/src/cursor.rs | 44 ++++++---- crates/oxc_parser/src/js/class.rs | 2 +- crates/oxc_parser/src/js/declaration.rs | 57 ++++--------- crates/oxc_parser/src/js/statement.rs | 104 ++++++++++++------------ crates/oxc_parser/src/modifiers.rs | 12 +-- crates/oxc_parser/src/ts/statement.rs | 29 +++---- crates/oxc_parser/src/ts/types.rs | 5 +- 7 files changed, 116 insertions(+), 137 deletions(-) diff --git a/crates/oxc_parser/src/cursor.rs b/crates/oxc_parser/src/cursor.rs index ce4f3e31ca7f4..cb2ce9a14e5af 100644 --- a/crates/oxc_parser/src/cursor.rs +++ b/crates/oxc_parser/src/cursor.rs @@ -158,8 +158,7 @@ impl<'a> ParserImpl<'a> { #[inline] pub(crate) fn can_insert_semicolon(&self) -> bool { let token = self.cur_token(); - let kind = token.kind(); - kind == Kind::Semicolon || kind == Kind::RCurly || kind.is_eof() || token.is_on_new_line() + matches!(token.kind(), Kind::Semicolon | Kind::RCurly | Kind::Eof) || token.is_on_new_line() } /// # Errors @@ -323,23 +322,37 @@ impl<'a> ParserImpl<'a> { } pub(crate) fn parse_normal_list(&mut self, open: Kind, close: Kind, f: F) -> Vec<'a, T> + where + F: Fn(&mut Self) -> T, + { + self.expect(open); + let mut list = self.ast.vec(); + while !self.at(close) && !self.has_fatal_error() { + list.push(f(self)); + } + self.expect(close); + list + } + + pub(crate) fn parse_normal_list_breakable( + &mut self, + open: Kind, + close: Kind, + f: F, + ) -> Vec<'a, T> where F: Fn(&mut Self) -> Option, { self.expect(open); let mut list = self.ast.vec(); loop { - let kind = self.cur_kind(); - if kind == close || self.has_fatal_error() { + if self.at(close) || self.has_fatal_error() { break; } - match f(self) { - Some(e) => { - list.push(e); - } - None => { - break; - } + if let Some(e) = f(self) { + list.push(e); + } else { + break; } } self.expect(close); @@ -387,11 +400,7 @@ impl<'a> ParserImpl<'a> { let mut rest: Option> = None; let mut first = true; loop { - let kind = self.cur_kind(); - if self.has_fatal_error() { - break; - } - if kind == close { + if self.at(close) || self.has_fatal_error() { break; } @@ -423,8 +432,7 @@ impl<'a> ParserImpl<'a> { } if self.at(Kind::Dot3) { - let r = self.parse_rest_element(); - rest.replace(r); + rest.replace(self.parse_rest_element()); } else { list.push(parse_element(self)); } diff --git a/crates/oxc_parser/src/js/class.rs b/crates/oxc_parser/src/js/class.rs index 4d34935562dba..30eb1fbeb1cd8 100644 --- a/crates/oxc_parser/src/js/class.rs +++ b/crates/oxc_parser/src/js/class.rs @@ -187,7 +187,7 @@ impl<'a> ParserImpl<'a> { fn parse_class_body(&mut self) -> Box<'a, ClassBody<'a>> { let span = self.start_span(); - let class_elements = self.parse_normal_list(Kind::LCurly, Kind::RCurly, |p| { + let class_elements = self.parse_normal_list_breakable(Kind::LCurly, Kind::RCurly, |p| { // Skip empty class element `;` if p.eat(Kind::Semicolon) { while p.eat(Kind::Semicolon) {} diff --git a/crates/oxc_parser/src/js/declaration.rs b/crates/oxc_parser/src/js/declaration.rs index 5f27e715da2fc..5be9267bbaa5e 100644 --- a/crates/oxc_parser/src/js/declaration.rs +++ b/crates/oxc_parser/src/js/declaration.rs @@ -3,11 +3,7 @@ use oxc_ast::{NONE, ast::*}; use oxc_span::GetSpan; use super::VariableDeclarationParent; -use crate::{ - ParserImpl, StatementContext, diagnostics, - lexer::Kind, - modifiers::{ModifierFlags, Modifiers}, -}; +use crate::{ParserImpl, StatementContext, diagnostics, lexer::Kind}; impl<'a> ParserImpl<'a> { pub(crate) fn parse_let(&mut self, stmt_ctx: StatementContext) -> Statement<'a> { @@ -78,7 +74,7 @@ impl<'a> ParserImpl<'a> { start_span: u32, kind: VariableDeclarationKind, decl_parent: VariableDeclarationParent, - modifiers: &Modifiers<'a>, + declare: bool, ) -> Box<'a, VariableDeclaration<'a>> { let mut declarations = self.ast.vec(); loop { @@ -92,19 +88,7 @@ impl<'a> ParserImpl<'a> { if matches!(decl_parent, VariableDeclarationParent::Statement) { self.asi(); } - - self.verify_modifiers( - modifiers, - ModifierFlags::DECLARE, - diagnostics::modifier_cannot_be_used_here, - ); - - self.ast.alloc_variable_declaration( - self.end_span(start_span), - kind, - declarations, - modifiers.contains_declare(), - ) + self.ast.alloc_variable_declaration(self.end_span(start_span), kind, declarations, declare) } fn parse_variable_declarator( @@ -181,28 +165,24 @@ impl<'a> ParserImpl<'a> { let span = self.start_span(); let is_await = self.eat(Kind::Await); + let kind = if is_await { + VariableDeclarationKind::AwaitUsing + } else { + VariableDeclarationKind::Using + }; self.expect(Kind::Using); // BindingList[?In, ?Yield, ?Await, ~Pattern] - let mut declarations: oxc_allocator::Vec<'_, VariableDeclarator<'_>> = self.ast.vec(); + let mut declarations = self.ast.vec(); loop { - let declaration = self.parse_variable_declarator( - VariableDeclarationParent::Statement, - if is_await { - VariableDeclarationKind::AwaitUsing - } else { - VariableDeclarationKind::Using - }, - ); - - match declaration.id.kind { - BindingPatternKind::BindingIdentifier(_) => {} - _ => { - self.error(diagnostics::invalid_identifier_in_using_declaration( - declaration.id.span(), - )); - } + let declaration = + self.parse_variable_declarator(VariableDeclarationParent::Statement, kind); + + if !matches!(declaration.id.kind, BindingPatternKind::BindingIdentifier(_)) { + self.error(diagnostics::invalid_identifier_in_using_declaration( + declaration.id.span(), + )); } // Excluding `for` loops, an initializer is required in a UsingDeclaration. @@ -218,11 +198,6 @@ impl<'a> ParserImpl<'a> { } } - let kind = if is_await { - VariableDeclarationKind::AwaitUsing - } else { - VariableDeclarationKind::Using - }; self.ast.variable_declaration(self.end_span(span), kind, declarations, false) } } diff --git a/crates/oxc_parser/src/js/statement.rs b/crates/oxc_parser/src/js/statement.rs index 899926fe54838..fd4df25ec415f 100644 --- a/crates/oxc_parser/src/js/statement.rs +++ b/crates/oxc_parser/src/js/statement.rs @@ -91,7 +91,8 @@ impl<'a> ParserImpl<'a> { Kind::Do => self.parse_do_while_statement(), Kind::While => self.parse_while_statement(), Kind::For => self.parse_for_statement(), - Kind::Break | Kind::Continue => self.parse_break_or_continue_statement(), + Kind::Continue => self.parse_continue_statement(), + Kind::Break => self.parse_break_statement(), Kind::With => self.parse_with_statement(), Kind::Switch => self.parse_switch_statement(), Kind::Throw => self.parse_throw_statement(), @@ -206,13 +207,9 @@ impl<'a> ParserImpl<'a> { /// Section 14.2 Block Statement pub(crate) fn parse_block(&mut self) -> Box<'a, BlockStatement<'a>> { let span = self.start_span(); - self.expect(Kind::LCurly); - let mut body = self.ast.vec(); - while !self.at(Kind::RCurly) && !self.has_fatal_error() { - let stmt = self.parse_statement_list_item(StatementContext::StatementList); - body.push(stmt); - } - self.expect(Kind::RCurly); + let body = self.parse_normal_list(Kind::LCurly, Kind::RCurly, |p| { + p.parse_statement_list_item(StatementContext::StatementList) + }); self.ast.alloc_block_statement(self.end_span(span), body) } @@ -232,7 +229,7 @@ impl<'a> ParserImpl<'a> { start_span, kind, VariableDeclarationParent::Statement, - &Modifiers::empty(), + false, ); if stmt_ctx.is_single_statement() && decl.kind.is_lexical() { @@ -315,10 +312,25 @@ impl<'a> ParserImpl<'a> { // `for (const` | `for (var` match self.cur_kind() { - Kind::Const | Kind::Var => { + Kind::Const => { + let start_span = self.start_span(); + self.bump_any(); + return self.parse_variable_declaration_for_statement( + span, + start_span, + VariableDeclarationKind::Const, + r#await, + ); + } + Kind::Var => { let start_span = self.start_span(); - return self - .parse_variable_declaration_for_statement(span, start_span, None, r#await); + self.bump_any(); + return self.parse_variable_declaration_for_statement( + span, + start_span, + VariableDeclarationKind::Var, + r#await, + ); } Kind::Let => { // `for (let` @@ -330,7 +342,7 @@ impl<'a> ParserImpl<'a> { return self.parse_variable_declaration_for_statement( span, start_span, - Some(VariableDeclarationKind::Let), + VariableDeclarationKind::Let, r#await, ); } @@ -403,40 +415,25 @@ impl<'a> ParserImpl<'a> { &mut self, span: u32, start_span: u32, - decl_kind: Option, + decl_kind: VariableDeclarationKind, r#await: bool, ) -> Statement<'a> { let init_declaration = self.context(Context::empty(), Context::In, |p| { - let decl_ctx = VariableDeclarationParent::For; - let kind = if let Some(kind) = decl_kind { - kind - } else { - let kind = p.get_variable_declaration_kind(); - p.bump_any(); - kind - }; - p.parse_variable_declaration(start_span, kind, decl_ctx, &Modifiers::empty()) + p.parse_variable_declaration( + start_span, + decl_kind, + VariableDeclarationParent::For, + false, + ) }); self.parse_any_for_loop(span, init_declaration, r#await) } fn is_using_declaration(&mut self) -> bool { - self.lookahead(|p| { - p.is_next_token_binding_identifier_or_start_of_object_destructuring_on_same_line(false) - }) - } - - fn is_next_token_binding_identifier_or_start_of_object_destructuring_on_same_line( - &mut self, - disallow_of: bool, - ) -> bool { - self.bump_any(); - if disallow_of && self.at(Kind::Of) { - return false; - } - (self.cur_kind().is_binding_identifier() || self.at(Kind::LCurly)) - && !self.cur_token().is_on_new_line() + let token = self.lexer.peek_token(); + let kind = token.kind(); + (kind.is_binding_identifier() || kind == Kind::LCurly) && !token.is_on_new_line() } fn parse_using_declaration_for_statement(&mut self, span: u32, r#await: bool) -> Statement<'a> { @@ -495,10 +492,10 @@ impl<'a> ParserImpl<'a> { self.check_missing_initializer(d); } } - let test = if !self.at(Kind::Semicolon) && !self.at(Kind::RParen) { - Some(self.context(Context::In, Context::empty(), ParserImpl::parse_expr)) - } else { + let test = if matches!(self.cur_kind(), Kind::Semicolon | Kind::RParen) { None + } else { + Some(self.context(Context::In, Context::empty(), ParserImpl::parse_expr)) }; self.expect(Kind::Semicolon); let update = if self.at(Kind::RParen) { @@ -549,20 +546,23 @@ impl<'a> ParserImpl<'a> { } /// Section 14.8 Continue Statement + fn parse_continue_statement(&mut self) -> Statement<'a> { + let span = self.start_span(); + self.bump_any(); // bump `continue` + let label = + if self.can_insert_semicolon() { None } else { Some(self.parse_label_identifier()) }; + self.asi(); + self.ast.statement_continue(self.end_span(span), label) + } + /// Section 14.9 Break Statement - fn parse_break_or_continue_statement(&mut self) -> Statement<'a> { + fn parse_break_statement(&mut self) -> Statement<'a> { let span = self.start_span(); - let kind = self.cur_kind(); - self.bump_any(); // bump `break` or `continue` + self.bump_any(); // bump `break` let label = if self.can_insert_semicolon() { None } else { Some(self.parse_label_identifier()) }; self.asi(); - let end_span = self.end_span(span); - match kind { - Kind::Break => self.ast.statement_break(end_span, label), - Kind::Continue => self.ast.statement_continue(end_span, label), - _ => unreachable!(), - } + self.ast.statement_break(self.end_span(span), label) } /// Section 14.10 Return Statement @@ -604,7 +604,7 @@ impl<'a> ParserImpl<'a> { self.ast.statement_switch(self.end_span(span), discriminant, cases) } - pub(crate) fn parse_switch_case(&mut self) -> Option> { + pub(crate) fn parse_switch_case(&mut self) -> SwitchCase<'a> { let span = self.start_span(); let test = match self.cur_kind() { Kind::Default => { @@ -626,7 +626,7 @@ impl<'a> ParserImpl<'a> { let stmt = self.parse_statement_list_item(StatementContext::StatementList); consequent.push(stmt); } - Some(self.ast.switch_case(self.end_span(span), test, consequent)) + self.ast.switch_case(self.end_span(span), test, consequent) } /// Section 14.14 Throw Statement diff --git a/crates/oxc_parser/src/modifiers.rs b/crates/oxc_parser/src/modifiers.rs index f59af5c472769..89d35ebc46b81 100644 --- a/crates/oxc_parser/src/modifiers.rs +++ b/crates/oxc_parser/src/modifiers.rs @@ -159,12 +159,8 @@ impl<'a> Modifiers<'a> { /// `modifiers`. E.g., if `modifiers` is empty, then so is `flags``. #[must_use] pub(crate) fn new(modifiers: Option>, flags: ModifierFlags) -> Self { - if let Some(modifiers) = modifiers { - Self { modifiers: Some(modifiers), flags } - } else { - debug_assert!(flags.is_empty()); - Self { modifiers: None, flags: ModifierFlags::empty() } - } + debug_assert_eq!(modifiers.is_none(), flags.is_empty()); + Self { modifiers, flags } } pub fn empty() -> Self { @@ -297,10 +293,10 @@ impl std::fmt::Display for ModifierKind { impl<'a> ParserImpl<'a> { pub(crate) fn eat_modifiers_before_declaration(&mut self) -> Modifiers<'a> { - let mut flags = ModifierFlags::empty(); if !self.at_modifier() { - return Modifiers::new(None, flags); + return Modifiers::empty(); } + let mut flags = ModifierFlags::empty(); let mut modifiers = self.ast.vec(); while self.at_modifier() { let span = self.start_span(); diff --git a/crates/oxc_parser/src/ts/statement.rs b/crates/oxc_parser/src/ts/statement.rs index e9106f9786491..9bd125421a518 100644 --- a/crates/oxc_parser/src/ts/statement.rs +++ b/crates/oxc_parser/src/ts/statement.rs @@ -220,9 +220,8 @@ impl<'a> ParserImpl<'a> { fn parse_ts_interface_body(&mut self) -> Box<'a, TSInterfaceBody<'a>> { let span = self.start_span(); - let body_list = self.parse_normal_list(Kind::LCurly, Kind::RCurly, |p| { - Some(Self::parse_ts_type_signature(p)) - }); + let body_list = + self.parse_normal_list(Kind::LCurly, Kind::RCurly, Self::parse_ts_type_signature); self.ast.alloc_ts_interface_body(self.end_span(span), body_list) } @@ -433,11 +432,16 @@ impl<'a> ParserImpl<'a> { Kind::Var | Kind::Let | Kind::Const => { let kind = self.get_variable_declaration_kind(); self.bump_any(); + self.verify_modifiers( + modifiers, + ModifierFlags::DECLARE, + diagnostics::modifier_cannot_be_used_here, + ); let decl = self.parse_variable_declaration( start_span, kind, VariableDeclarationParent::Statement, - modifiers, + modifiers.contains_declare(), ); Declaration::VariableDeclaration(decl) } @@ -450,16 +454,13 @@ impl<'a> ParserImpl<'a> { let token = self.cur_token(); let mut import_kind = ImportOrExportKind::Value; let mut identifier = self.parse_binding_identifier(); - if self.is_ts && token.kind() == Kind::Type { - // `import type ...` - if self.cur_kind().is_binding_identifier() { - // `import type something ...` - identifier = self.parse_binding_identifier(); - import_kind = ImportOrExportKind::Type; - } else { - // `import type = ...` - import_kind = ImportOrExportKind::Value; - } + if self.is_ts + && token.kind() == Kind::Type + && self.cur_kind().is_binding_identifier() + { + // `import type something ...` + identifier = self.parse_binding_identifier(); + import_kind = ImportOrExportKind::Type; } self.parse_ts_import_equals_declaration(import_kind, identifier, start_span) } diff --git a/crates/oxc_parser/src/ts/types.rs b/crates/oxc_parser/src/ts/types.rs index 2078eef735830..645af9b9868e8 100644 --- a/crates/oxc_parser/src/ts/types.rs +++ b/crates/oxc_parser/src/ts/types.rs @@ -653,9 +653,8 @@ impl<'a> ParserImpl<'a> { fn parse_type_literal(&mut self) -> TSType<'a> { let span = self.start_span(); - let member_list = self.parse_normal_list(Kind::LCurly, Kind::RCurly, |p| { - Some(Self::parse_ts_type_signature(p)) - }); + let member_list = + self.parse_normal_list(Kind::LCurly, Kind::RCurly, Self::parse_ts_type_signature); self.ast.ts_type_type_literal(self.end_span(span), member_list) } From e3473b673a3c6fd5e4473daf0ae68271701b7093 Mon Sep 17 00:00:00 2001 From: Ulrich Stark Date: Fri, 26 Sep 2025 16:26:49 +0200 Subject: [PATCH 2/2] trigger benchmark again