perf(parser): factor object type opening brace (#28430)

Consume the shared opening brace once for object and mapped types, avoiding lookahead for ordinary named properties.

Local arm64: simple/nested objects take 6–9% less parse time (4–7% with tokens); ordinary corpus −0.59%, token mode flat. Largest slower control: +1.7% with tokens.

Validated parser and Node suites, unchanged conformance and allocations, and 49,776 differential comparisons.

AI assistance: Codex.
This commit is contained in:
Boshen committed 2026-10-29 16:39:54 +00:00
1 parent 028a2678b6
commit 30f59f4da1
2 files changed
+22 -15

No files matched your search

+1 -1
View File
@@ -408,7 +408,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
#[expect(clippy::inline_always)]
#[inline(always)]
fn parse_normal_list_into<F, T>(
pub(crate) fn parse_normal_list_into<F, T>(
&mut self,
list: &mut ArenaVec<'a, T>,
close: Kind,
+21 -14
View File
@@ -1,6 +1,6 @@
use oxc_allocator::{ArenaBox, ArenaVec};
use oxc_ast::ast::*;
use oxc_span::{GetSpan, SPAN};
use oxc_span::{GetSpan, SPAN, Span};
use oxc_str::Ident;
use oxc_syntax::operator::UnaryOperator;
@@ -498,10 +498,12 @@ impl<'a, C: Config> ParserImpl<'a, C> {
self.parse_type_query()
}
Kind::LCurly => {
if self.lookahead(Self::is_start_of_mapped_type) {
self.parse_mapped_type()
let opening_span = self.cur_token().span();
self.bump_any();
if self.is_start_of_mapped_type() {
self.parse_mapped_type(opening_span.start)
} else {
self.parse_type_literal()
self.parse_type_literal(opening_span)
}
}
Kind::LBrack => self.parse_tuple_type(),
@@ -622,7 +624,15 @@ impl<'a, C: Config> ParserImpl<'a, C> {
}
fn is_start_of_mapped_type(&mut self) -> bool {
self.bump_any();
// The opening brace is shared with type literals. Ordinary named members cannot
// begin a mapped type, so only speculate for the remaining forms.
if self.cur_kind() != Kind::Readonly && self.cur_kind().is_identifier_name() {
return false;
}
self.lookahead(Self::is_start_of_mapped_type_worker)
}
fn is_start_of_mapped_type_worker(&mut self) -> bool {
let kind = self.cur_kind();
if kind == Kind::Plus || kind == Kind::Minus {
self.bump_any();
@@ -655,10 +665,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|| self.is_start_of_type(!is_js_doc_parameter)
}
fn parse_mapped_type(&mut self) -> TSType<'a> {
debug_assert!(self.at(Kind::LCurly));
let start = self.cur_start();
self.advance(Kind::LCurly);
fn parse_mapped_type(&mut self, start: u32) -> TSType<'a> {
let mut readonly = None;
if self.eat(Kind::Readonly) {
readonly = Some(TSMappedTypeModifierOperator::True);
@@ -713,11 +720,11 @@ impl<'a, C: Config> ParserImpl<'a, C> {
)
}
fn parse_type_literal(&mut self) -> TSType<'a> {
let start = self.cur_start();
let member_list =
self.parse_normal_list(Kind::LCurly, Kind::RCurly, Self::parse_ts_type_signature);
TSType::new_ts_type_literal(self.end_span(start), member_list, self)
fn parse_type_literal(&mut self, opening_span: Span) -> TSType<'a> {
let mut member_list = ArenaVec::new_in(self);
self.parse_normal_list_into(&mut member_list, Kind::RCurly, Self::parse_ts_type_signature);
self.expect_closing(Kind::RCurly, opening_span);
TSType::new_ts_type_literal(self.end_span(opening_span.start), member_list, self)
}
fn parse_type_query(&mut self) -> TSType<'a> {