mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
perf(parser): fast-path leaf expressions before delimiters (#28432)
Parse leaf operands once and use token kinds to detect completed expressions, sharing continuation handling with normal expression parsing. Local 14-fixture benchmarks show ~2.4% less parse time with identifier hashes off and ~2.6% with hashes on; `typescript.js` uses ~4.8% fewer instructions. AI-assisted.
This commit is contained in:
1 parent
1bcccf1e1b
commit
6e35e3c268
1 file changed
+98
-61
@@ -1402,11 +1402,9 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
let lhs = if kind == Kind::Ident {
|
||||
// Identifiers cannot start a unary or prefix-update expression. Parse their
|
||||
// suffixes directly, keeping pure annotations inside the postfix update.
|
||||
let lhs = self.with_pure_comments(|p| {
|
||||
let primary = p.parse_identifier_expression();
|
||||
p.parse_lhs_expression_rest(lhs_start, primary)
|
||||
});
|
||||
self.parse_postfix_update_expression(lhs_start, lhs)
|
||||
let pure_comments = self.lexer.trivia_builder.previous_token_pure_comments();
|
||||
let primary = self.parse_identifier_expression();
|
||||
self.parse_primary_expression_rest(lhs_start, primary, pure_comments)
|
||||
} else if self.ctx.has_in() && kind == Kind::PrivateIdentifier {
|
||||
// [+In] PrivateIdentifier in ShiftExpression[?Yield, ?Await]
|
||||
self.parse_private_in_expression(lhs_start, lhs_precedence)
|
||||
@@ -1416,30 +1414,10 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
{
|
||||
self.parse_unary_expression_or_higher(lhs_start)
|
||||
} else {
|
||||
// Most operands start with a primary expression. Skip unary/update dispatch, and
|
||||
// only enter member/call parsing when the following token can extend the primary.
|
||||
// Most operands start with a primary expression. Skip unary/update dispatch.
|
||||
let pure_comments = self.lexer.trivia_builder.previous_token_pure_comments();
|
||||
let primary = self.parse_primary_expression();
|
||||
let mut lhs = if matches!(
|
||||
self.cur_kind(),
|
||||
Kind::Dot
|
||||
| Kind::LBrack
|
||||
| Kind::LParen
|
||||
| Kind::QuestionDot
|
||||
| Kind::NoSubstitutionTemplate
|
||||
| Kind::TemplateHead
|
||||
| Kind::Bang
|
||||
| Kind::LAngle
|
||||
| Kind::ShiftLeft
|
||||
) {
|
||||
self.parse_lhs_expression_rest(lhs_start, primary)
|
||||
} else {
|
||||
primary
|
||||
};
|
||||
if let Some(comments) = pure_comments {
|
||||
self.apply_pure_comments_to_call(comments, &mut lhs);
|
||||
}
|
||||
self.parse_postfix_update_expression(lhs_start, lhs)
|
||||
self.parse_primary_expression_rest(lhs_start, primary, pure_comments)
|
||||
};
|
||||
|
||||
if kind_to_precedence(self.cur_kind()).is_none() {
|
||||
@@ -1448,6 +1426,36 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
self.parse_binary_expression_rest(lhs_start, lhs, lhs_parenthesized, lhs_precedence)
|
||||
}
|
||||
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
fn parse_primary_expression_rest(
|
||||
&mut self,
|
||||
start: u32,
|
||||
primary: Expression<'a>,
|
||||
pure_comments: Option<(u32, NonZeroU32)>,
|
||||
) -> Expression<'a> {
|
||||
let mut lhs = if matches!(
|
||||
self.cur_kind(),
|
||||
Kind::Dot
|
||||
| Kind::LBrack
|
||||
| Kind::LParen
|
||||
| Kind::QuestionDot
|
||||
| Kind::NoSubstitutionTemplate
|
||||
| Kind::TemplateHead
|
||||
| Kind::Bang
|
||||
| Kind::LAngle
|
||||
| Kind::ShiftLeft
|
||||
) {
|
||||
self.parse_lhs_expression_rest(start, primary)
|
||||
} else {
|
||||
primary
|
||||
};
|
||||
if let Some(comments) = pure_comments {
|
||||
self.apply_pure_comments_to_call(comments, &mut lhs);
|
||||
}
|
||||
self.parse_postfix_update_expression(start, lhs)
|
||||
}
|
||||
|
||||
/// Section 13.6 - 13.13 Binary Expression
|
||||
fn parse_binary_expression_rest(
|
||||
&mut self,
|
||||
@@ -1628,42 +1636,71 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
) -> Expression<'a> {
|
||||
let no_side_effects_comments =
|
||||
self.lexer.trivia_builder.previous_token_no_side_effects_comments();
|
||||
// [+Yield] YieldExpression
|
||||
if self.is_yield_expression() {
|
||||
return self.parse_yield_expression();
|
||||
}
|
||||
// `() => {}`, `(x) => {}`
|
||||
if matches!(self.cur_kind(), Kind::LParen | Kind::LAngle | Kind::Async)
|
||||
&& let Some(mut arrow_expr) = self.try_parse_parenthesized_arrow_function_expression(
|
||||
allow_return_type_in_arrow_function,
|
||||
)
|
||||
{
|
||||
if let Some(comments) = no_side_effects_comments
|
||||
&& let Expression::ArrowFunctionExpression(func) = &mut arrow_expr
|
||||
{
|
||||
func.pure = true;
|
||||
self.lexer.trivia_builder.mark_no_side_effects_comments_applied(comments);
|
||||
}
|
||||
return arrow_expr;
|
||||
}
|
||||
// `async x => {}`
|
||||
if self.at(Kind::Async)
|
||||
&& let Some(mut arrow_expr) = self.try_parse_async_simple_arrow_function_expression(
|
||||
allow_return_type_in_arrow_function,
|
||||
)
|
||||
{
|
||||
if let Some(comments) = no_side_effects_comments
|
||||
&& let Expression::ArrowFunctionExpression(func) = &mut arrow_expr
|
||||
{
|
||||
func.pure = true;
|
||||
self.lexer.trivia_builder.mark_no_side_effects_comments_applied(comments);
|
||||
}
|
||||
return arrow_expr;
|
||||
}
|
||||
|
||||
let start = self.cur_start();
|
||||
let lhs_parenthesized = self.at(Kind::LParen);
|
||||
let lhs = self.parse_binary_expression_or_higher(Precedence::Comma);
|
||||
let kind = self.cur_kind();
|
||||
let lhs = if matches!(kind, Kind::Ident | Kind::This) || kind.is_literal() {
|
||||
let pure_comments = self.lexer.trivia_builder.previous_token_pure_comments();
|
||||
let primary = match kind {
|
||||
Kind::Ident => self.parse_plain_identifier_expression(),
|
||||
Kind::This => self.parse_this_expression(),
|
||||
_ => self.parse_literal_expression(),
|
||||
};
|
||||
// The leaf is complete when the next token is an expression delimiter.
|
||||
if matches!(
|
||||
self.cur_kind(),
|
||||
Kind::Comma
|
||||
| Kind::RParen
|
||||
| Kind::RBrack
|
||||
| Kind::RCurly
|
||||
| Kind::Semicolon
|
||||
| Kind::Colon
|
||||
) {
|
||||
return primary;
|
||||
}
|
||||
let lhs = self.parse_primary_expression_rest(start, primary, pure_comments);
|
||||
if kind_to_precedence(self.cur_kind()).is_none() {
|
||||
lhs
|
||||
} else {
|
||||
self.parse_binary_expression_rest(start, lhs, false, Precedence::Comma)
|
||||
}
|
||||
} else {
|
||||
// [+Yield] YieldExpression
|
||||
if self.is_yield_expression() {
|
||||
return self.parse_yield_expression();
|
||||
}
|
||||
// `() => {}`, `(x) => {}`
|
||||
if matches!(self.cur_kind(), Kind::LParen | Kind::LAngle | Kind::Async)
|
||||
&& let Some(mut arrow_expr) = self
|
||||
.try_parse_parenthesized_arrow_function_expression(
|
||||
allow_return_type_in_arrow_function,
|
||||
)
|
||||
{
|
||||
if let Some(comments) = no_side_effects_comments
|
||||
&& let Expression::ArrowFunctionExpression(func) = &mut arrow_expr
|
||||
{
|
||||
func.pure = true;
|
||||
self.lexer.trivia_builder.mark_no_side_effects_comments_applied(comments);
|
||||
}
|
||||
return arrow_expr;
|
||||
}
|
||||
// `async x => {}`
|
||||
if self.at(Kind::Async)
|
||||
&& let Some(mut arrow_expr) = self.try_parse_async_simple_arrow_function_expression(
|
||||
allow_return_type_in_arrow_function,
|
||||
)
|
||||
{
|
||||
if let Some(comments) = no_side_effects_comments
|
||||
&& let Expression::ArrowFunctionExpression(func) = &mut arrow_expr
|
||||
{
|
||||
func.pure = true;
|
||||
self.lexer.trivia_builder.mark_no_side_effects_comments_applied(comments);
|
||||
}
|
||||
return arrow_expr;
|
||||
}
|
||||
|
||||
self.parse_binary_expression_or_higher(Precedence::Comma)
|
||||
};
|
||||
let lhs_parenthesized_span = lhs_parenthesized.then(|| self.end_span(start));
|
||||
let kind = self.cur_kind();
|
||||
|
||||
|
||||
Reference in new issue
Block a user