perf(parser): combine parenthesized arrow lookahead (#28412)

Classify parenthesized arrow heads in one lexer checkpoint, avoiding a duplicate token scan.

Local arm64 parse time: ordinary corpus −0.67% (−0.39% with tokens); controlled empty arrow fixture −11.7%, parenthesized string fixture +1.4%.

Validated parser and Node suites, unchanged conformance and allocations, and 82,200 differential comparisons.

AI assistance: Codex.
This commit is contained in:
Boshen committed 2026-10-29 13:29:01 +00:00
1 parent e29d736998
commit de75d569a0
1 file changed
+97 -91
+97 -91
View File
@@ -64,14 +64,17 @@ impl<'a, C: Config> ParserImpl<'a, C> {
fn is_parenthesized_arrow_function_expression(&mut self) -> Tristate {
match self.cur_kind() {
Kind::LParen => {
// `(1 + a)` can never be arrow parameters: the leading literal is not the start of
// a `BindingElement`, so the worker would bump past it and return `Tristate::False`
// (`!second.is_binding_identifier() && second != This`). Skip the lookahead.
if self.lexer.peek_token().kind().is_literal() {
// This check only reads lexer tokens. Keep one checkpoint for both the
// literal fast path and the remaining lookahead, so the first token is read once.
let checkpoint = self.lexer.checkpoint();
let second = self.lexer.next_token().kind();
let result = if second.is_literal() {
Tristate::False
} else {
self.lookahead(Self::is_parenthesized_arrow_function_expression_worker)
}
self.is_parenthesized_arrow_function_expression_after_lparen(second, false)
};
self.lexer.rewind(checkpoint);
result
}
Kind::LAngle | Kind::Async => {
self.lookahead(Self::is_parenthesized_arrow_function_expression_worker)
@@ -80,6 +83,93 @@ impl<'a, C: Config> ParserImpl<'a, C> {
}
}
// `second` follows `(` and the lexer is positioned after it. Only lexer state changes;
// callers must rewind their checkpoint before parsing the expression.
fn is_parenthesized_arrow_function_expression_after_lparen(
&mut self,
second: Kind,
is_async: bool,
) -> Tristate {
match second {
// Simple cases: "() =>", "(): ", and "() {".
// This is an arrow function with no parameters.
// The last one is not actually an arrow function,
// but this is probably what the user intended.
Kind::RParen => {
let third = self.lexer.next_token().kind();
match third {
// `async()` can be a call on the true side of a conditional.
Kind::Colon if self.is_ts && is_async => Tristate::Maybe,
// Bare `()` cannot be a parenthesized expression, so the colon
// must start a return type, even inside a conditional expression.
Kind::Colon if self.is_ts => Tristate::True,
Kind::Arrow | Kind::LCurly => Tristate::True,
_ => Tristate::False,
}
}
// If encounter "([" or "({", this could be the start of a binding pattern.
// Examples:
// ([ x ]) => { }
// ({ x }) => { }
// ([ x ])
// ({ x })
Kind::LBrack | Kind::LCurly => Tristate::Maybe,
// Simple case: "(..."
// This is an arrow function with a rest parameter.
Kind::Dot3 => {
let third = self.lexer.next_token().kind();
match third {
// '(...ident' is a lambda
Kind::Ident => Tristate::True,
// '(...null' is not a lambda
kind if kind.is_literal() => Tristate::False,
_ => Tristate::Maybe,
}
}
_ => {
let third = self.lexer.next_token().kind();
// Check for "(xxx yyy", where xxx is a modifier and yyy is an identifier. This
// isn't actually allowed, but we want to treat it as a lambda so we can provide
// a good error message.
if second.is_modifier_kind()
&& second != Kind::Async
&& third.is_binding_identifier()
{
if third == Kind::As {
return Tristate::False; // https://github.com/microsoft/TypeScript/issues/44466
}
return Tristate::True;
}
// If we had "(" followed by something that's not an identifier,
// then this definitely doesn't look like a lambda. "this" is not
// valid, but we want to parse it and then give a semantic error.
if !second.is_binding_identifier() && second != Kind::This {
return Tristate::False;
}
match third {
// If we have something like "(a:", then we must have a
// type-annotated parameter in an arrow function expression.
Kind::Colon => Tristate::True,
// If we have "(a?:" or "(a?," or "(a?=" or "(a?)" then it is definitely a lambda.
Kind::Question => {
let fourth = self.lexer.next_token().kind();
if matches!(fourth, Kind::Colon | Kind::Comma | Kind::Eq | Kind::RParen) {
return Tristate::True;
}
Tristate::False
}
// If we have "(a," or "(a=" or "(a)" this *could* be an arrow function
Kind::Comma | Kind::Eq | Kind::RParen => Tristate::Maybe,
// It is definitely not an arrow function
_ => Tristate::False,
}
}
}
}
fn is_parenthesized_arrow_function_expression_worker(&mut self) -> Tristate {
let is_async = self.eat(Kind::Async);
if is_async {
@@ -98,91 +188,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
match first {
Kind::LParen => {
match second {
// Simple cases: "() =>", "(): ", and "() {".
// This is an arrow function with no parameters.
// The last one is not actually an arrow function,
// but this is probably what the user intended.
Kind::RParen => {
self.bump_any();
let third = self.cur_kind();
match third {
// `async()` can be a call on the true side of a conditional.
Kind::Colon if self.is_ts && is_async => Tristate::Maybe,
// Bare `()` cannot be a parenthesized expression, so the colon
// must start a return type, even inside a conditional expression.
Kind::Colon if self.is_ts => Tristate::True,
Kind::Arrow | Kind::LCurly => Tristate::True,
_ => Tristate::False,
}
}
// If encounter "([" or "({", this could be the start of a binding pattern.
// Examples:
// ([ x ]) => { }
// ({ x }) => { }
// ([ x ])
// ({ x })
Kind::LBrack | Kind::LCurly => Tristate::Maybe,
// Simple case: "(..."
// This is an arrow function with a rest parameter.
Kind::Dot3 => {
self.bump_any();
let third = self.cur_kind();
match third {
// '(...ident' is a lambda
Kind::Ident => Tristate::True,
// '(...null' is not a lambda
kind if kind.is_literal() => Tristate::False,
_ => Tristate::Maybe,
}
}
_ => {
self.bump_any();
let third = self.cur_kind();
// Check for "(xxx yyy", where xxx is a modifier and yyy is an identifier. This
// isn't actually allowed, but we want to treat it as a lambda so we can provide
// a good error message.
if second.is_modifier_kind()
&& second != Kind::Async
&& third.is_binding_identifier()
{
if third == Kind::As {
return Tristate::False; // https://github.com/microsoft/TypeScript/issues/44466
}
return Tristate::True;
}
// If we had "(" followed by something that's not an identifier,
// then this definitely doesn't look like a lambda. "this" is not
// valid, but we want to parse it and then give a semantic error.
if !second.is_binding_identifier() && second != Kind::This {
return Tristate::False;
}
match third {
// If we have something like "(a:", then we must have a
// type-annotated parameter in an arrow function expression.
Kind::Colon => Tristate::True,
// If we have "(a?:" or "(a?," or "(a?=" or "(a?)" then it is definitely a lambda.
Kind::Question => {
self.bump_any();
let fourth = self.cur_kind();
if matches!(
fourth,
Kind::Colon | Kind::Comma | Kind::Eq | Kind::RParen
) {
return Tristate::True;
}
Tristate::False
}
// If we have "(a," or "(a=" or "(a)" this *could* be an arrow function
Kind::Comma | Kind::Eq | Kind::RParen => Tristate::Maybe,
// It is definitely not an arrow function
_ => Tristate::False,
}
}
}
self.is_parenthesized_arrow_function_expression_after_lparen(second, is_async)
}
Kind::LAngle => {
// If we have "<" not followed by an identifier,