perf(parser): build simple arrow bindings directly (#28493)

Build simple arrow parameters directly as bindings, avoiding a temporary
identifier-reference allocation. Based on #28491.

Validated parser conformance, annotations, bindings, and allocations.
AI-assisted.
This commit is contained in:
Boshen authored and GitHub committed 2026-10-30 17:17:28 +08:00
1 parent e1c9baf001
commit b3b5444712
8 files changed
+99 -26

No files matched your search

+15 -11
View File
@@ -38,14 +38,17 @@ impl<'a, C: Config> ParserImpl<'a, C> {
let start = self.cur_start();
self.bump_any(); // bump `async`
// Lookahead established a single binding identifier followed by `=>`.
// An ordinary identifier cannot extend into a member, call, or binary expression here.
let expr = if self.at(Kind::Ident) {
self.parse_plain_identifier_expression()
// Build the binding directly instead of allocating a temporary identifier reference.
let ident = if self.at(Kind::Ident) {
self.parse_plain_binding_identifier()
} else if self.cur_kind().is_identifier() {
self.parse_known_binding_identifier()
} else {
self.parse_binary_expression_or_higher(Precedence::Comma)
};
let Expression::Identifier(ident) = &expr else {
return self.unexpected();
let expr = self.parse_binary_expression_or_higher(Precedence::Comma);
let Expression::Identifier(ident) = &expr else {
return self.unexpected();
};
BindingIdentifier::new(ident.span, ident.name, self)
};
// It is a Syntax Error if ArrowParameters Contains AwaitExpression is true.
if ident.name == "await" {
@@ -256,14 +259,15 @@ impl<'a, C: Config> ParserImpl<'a, C> {
pub(crate) fn parse_simple_arrow_function_expression(
&mut self,
start: u32,
ident: &IdentifierReference<'a>,
ident: BindingIdentifier<'a>,
r#async: bool,
allow_return_type_in_arrow_function: bool,
) -> Expression<'a> {
let pattern = BindingPattern::new_binding_identifier(ident.span, ident.name, self);
let formal_parameter = FormalParameter::new_plain(ident.span, pattern, self);
let span = ident.span;
let pattern = BindingPattern::BindingIdentifier(self.alloc(ident));
let formal_parameter = FormalParameter::new_plain(span, pattern, self);
let params = FormalParameters::boxed(
ident.span,
span,
FormalParameterKind::ArrowFormalParameters,
[formal_parameter],
None,
+26 -2
View File
@@ -1642,7 +1642,30 @@ impl<'a, C: Config> ParserImpl<'a, C> {
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::Ident => {
let (span, name) = self.parse_identifier_kind(Kind::Ident);
// Only allocate an identifier reference once the following token
// establishes that this is an expression rather than an arrow head.
if self.at(Kind::Arrow) {
let binding = BindingIdentifier::new(span, name, self);
let mut arrow_expr = self.parse_simple_arrow_function_expression(
start,
binding,
/* async */ false,
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;
}
Expression::new_identifier(span, name, self)
}
Kind::This => self.parse_this_expression(),
_ => self.parse_literal_expression(),
};
@@ -1708,9 +1731,10 @@ impl<'a, C: Config> ParserImpl<'a, C> {
if kind == Kind::Arrow
&& let Expression::Identifier(ident) = &lhs
{
let binding = BindingIdentifier::new(ident.span, ident.name, self);
let mut arrow_expr = self.parse_simple_arrow_function_expression(
start,
ident,
binding,
/* async */ false,
allow_return_type_in_arrow_function,
);
+45
View File
@@ -1076,6 +1076,51 @@ mod test {
}
}
#[test]
fn simple_arrow_no_side_effects_annotations() {
for source in [
"/*#__NO_SIDE_EFFECTS__*/ value => value",
"/*#__NO_SIDE_EFFECTS__*/ async value => value",
"/*#__NO_SIDE_EFFECTS__*/ async of => of",
] {
let allocator = Allocator::default();
let expr =
Parser::new(&allocator, source, SourceType::default()).parse_expression().unwrap();
let Expression::ArrowFunctionExpression(arrow) = expr else { panic!("{source}") };
assert!(arrow.pure, "{source}");
}
}
#[test]
fn identifier_continuation_pure_annotations() {
for source in [
"/*#__PURE__*/ callee()",
"/*#__PURE__*/ callee<T>()",
"/*#__PURE__*/ callee!.method()",
] {
let allocator = Allocator::default();
let expr =
Parser::new(&allocator, source, SourceType::ts()).parse_expression().unwrap();
let Expression::CallExpression(call) = expr else { panic!("{source}") };
assert!(call.pure, "{source}");
}
// Check both the assignment-expression entry and a binary RHS continuation.
for (source, annotated_left) in [
("/*#__PURE__*/ callee() + other()", true),
("other() + /*#__PURE__*/ callee()", false),
] {
let allocator = Allocator::default();
let expr =
Parser::new(&allocator, source, SourceType::default()).parse_expression().unwrap();
let Expression::BinaryExpression(binary) = expr else { panic!("{source}") };
let Expression::CallExpression(left) = &binary.left else { panic!("{source}") };
let Expression::CallExpression(right) = &binary.right else { panic!("{source}") };
assert_eq!(left.pure, annotated_left, "{source}");
assert_eq!(right.pure, !annotated_left, "{source}");
}
}
#[test]
fn flow_error() {
let allocator = Allocator::default();
@@ -7,7 +7,7 @@ checker.ts:
sys peak growth: 2973842 # 2.97 MB
arena allocs: 36968
arena reallocs: 927
arena size: 38503224 # 38.50 MB
arena size: 38485784 # 38.49 MB
App.tsx:
file size: 415342 # 415.34 kB
@@ -40,7 +40,7 @@ pdf.mjs:
sys peak growth: 1156488 # 1.16 MB
arena allocs: 21652
arena reallocs: 402
arena size: 10987960 # 10.99 MB
arena size: 10983512 # 10.98 MB
antd.js:
file size: 6686316 # 6.69 MB
@@ -7,7 +7,7 @@ checker.ts:
sys peak growth: 10169560 # 10.17 MB
arena allocs: 56228
arena reallocs: 5084
arena size: 16339184 # 16.34 MB
arena size: 16321744 # 16.32 MB
App.tsx:
file size: 415342 # 415.34 kB
@@ -40,7 +40,7 @@ pdf.mjs:
sys peak growth: 3693355 # 3.69 MB
arena allocs: 19582
arena reallocs: 2574
arena size: 5666384 # 5.67 MB
arena size: 5661936 # 5.66 MB
antd.js:
file size: 6686316 # 6.69 MB
@@ -5,9 +5,9 @@ checker.ts:
sys deallocs: 19
sys alloc bytes: 4820 # 4.82 kB
sys peak growth: 2432 # 2.43 kB
arena allocs: 259857
arena allocs: 259312
arena reallocs: 15233
arena size: 12892944 # 12.89 MB
arena size: 12875504 # 12.88 MB
App.tsx:
file size: 415342 # 415.34 kB
@@ -38,9 +38,9 @@ pdf.mjs:
sys deallocs: 61
sys alloc bytes: 24020 # 24.02 kB
sys peak growth: 12140 # 12.14 kB
arena allocs: 89079
arena allocs: 88940
arena reallocs: 5904
arena size: 4492488 # 4.49 MB
arena size: 4488040 # 4.49 MB
antd.js:
file size: 6686316 # 6.69 MB
@@ -60,7 +60,7 @@ binder.ts:
sys deallocs: 0
sys alloc bytes: 3016 # 3.02 kB
sys peak growth: 251 # 251 B
arena allocs: 16465
arena allocs: 16454
arena reallocs: 1093
arena size: 915968 # 915.97 kB
@@ -7,7 +7,7 @@ checker.ts:
sys peak growth: 3858362 # 3.86 MB
arena allocs: 0
arena reallocs: 0
arena size: 12892944 # 12.89 MB
arena size: 12875504 # 12.88 MB
App.tsx:
file size: 415342 # 415.34 kB
@@ -40,7 +40,7 @@ pdf.mjs:
sys peak growth: 832762 # 832.76 kB
arena allocs: 0
arena reallocs: 0
arena size: 4492488 # 4.49 MB
arena size: 4488040 # 4.49 MB
antd.js:
file size: 6686316 # 6.69 MB
@@ -7,7 +7,7 @@ checker.ts:
sys peak growth: 2243 # 2.24 kB
arena allocs: 1959
arena reallocs: 5
arena size: 12981400 # 12.98 MB
arena size: 12963960 # 12.96 MB
App.tsx:
file size: 415342 # 415.34 kB
@@ -40,7 +40,7 @@ pdf.mjs:
sys peak growth: 1610 # 1.61 kB
arena allocs: 2
arena reallocs: 0
arena size: 4492512 # 4.49 MB
arena size: 4488064 # 4.49 MB
antd.js:
file size: 6686316 # 6.69 MB