perf(parser): skip redundant member-rest re-scan on call entry (#23068)

Follow-up to #23063.

`parse_lhs_expression_or_higher` parses the `MemberExpression` to completion before entering `parse_call_expression_rest`, and (since #23063) only enters it when the current token is `(` (Arguments) or `?.` (OptionalChain). The loop in `parse_call_expression_rest` then re-ran `parse_member_expression_rest` — the hottest function in the parser — as its first step on every iteration.

For a `(` entry that first call is the identity: `(` is not in the member-expression continuation FIRST set (`?.`, `.`, `[`, template, TS `!`/`<`). So skip it on the `(` first-entry. The `?.` entry and every later iteration (after `Arguments`) still run it.

Behavior-preserving:
- `cargo coverage -- parser` — Test262 / Babel / TypeScript / ESTree snapshots byte-identical (no `.snap` changes).
- `just allocs` — allocation snapshot unchanged.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Boshen committed 2026-06-08 09:04:28 +00:00
1 parent 8925998899
commit 8eb5507d0c
1 file changed
+18 -6
+18 -6
View File
@@ -1029,13 +1029,25 @@ impl<'a, C: Config> ParserImpl<'a, C> {
in_optional_chain: &mut bool,
) -> Expression<'a> {
let mut lhs = lhs;
// The caller only enters here with the current token being `(` or `?.`. For `(`,
// `parse_member_expression_rest` is a no-op (`(` ∉ its FIRST set: `?.`, `.`, `[`,
// template, TS `!`/`<`), so skip the redundant re-scan on entry; entering on `?.`, and
// every later iteration (after `Arguments`), still needs it.
debug_assert!(
matches!(self.cur_kind(), Kind::LParen | Kind::QuestionDot),
"parse_call_expression_rest is only entered on `(` or `?.`",
);
let mut rescan_members = !self.at(Kind::LParen);
while self.fatal_error.is_none() {
lhs = self.parse_member_expression_rest(
lhs_span,
lhs,
in_optional_chain,
/* allow_optional_chain */ true,
);
if rescan_members {
lhs = self.parse_member_expression_rest(
lhs_span,
lhs,
in_optional_chain,
/* allow_optional_chain */ true,
);
}
rescan_members = true;
let question_dot_span = self.at(Kind::QuestionDot).then(|| self.cur_token().span());
let question_dot = question_dot_span.is_some();
if question_dot {