diff --git a/.changeset/perf-es-parser-hot-paths.md b/.changeset/perf-es-parser-hot-paths.md new file mode 100644 index 000000000000..72d65578507a --- /dev/null +++ b/.changeset/perf-es-parser-hot-paths.md @@ -0,0 +1,7 @@ +--- +swc_common: patch +swc_core: patch +swc_ecma_parser: patch +--- + +perf(es/parser): Optimize parser hot paths diff --git a/.gitignore b/.gitignore index 5781a7ac4439..eda6be0ff089 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,6 @@ swc-core-*.tgz # samply profile.json profile.json.gz + +# Local micro-optimization profiler output +optimization-artifacts/ diff --git a/crates/swc_common/src/comments.rs b/crates/swc_common/src/comments.rs index df39f7fa06c8..81bc902b4064 100644 --- a/crates/swc_common/src/comments.rs +++ b/crates/swc_common/src/comments.rs @@ -29,6 +29,12 @@ use crate::{ /// We use [Option] instead of no-op Comments implementation to avoid allocation /// unless required. pub trait Comments { + /// Reserve storage for comments that will be inserted soon. + /// + /// Implementations may use this to avoid repeated map growth when a parser + /// has already buffered comments before publishing them. + fn reserve_comments(&self, _leading: usize, _trailing: usize) {} + fn add_leading(&self, pos: BytePos, cmt: Comment); fn add_leading_comments(&self, pos: BytePos, comments: Vec); fn has_leading(&self, pos: BytePos) -> bool; @@ -129,6 +135,10 @@ pub trait Comments { macro_rules! delegate { () => { + fn reserve_comments(&self, leading: usize, trailing: usize) { + (**self).reserve_comments(leading, trailing) + } + fn add_leading(&self, pos: BytePos, cmt: Comment) { (**self).add_leading(pos, cmt) } @@ -282,6 +292,12 @@ impl Comments for Option where C: Comments, { + fn reserve_comments(&self, leading: usize, trailing: usize) { + if let Some(c) = self { + c.reserve_comments(leading, trailing) + } + } + fn add_leading(&self, pos: BytePos, cmt: Comment) { if let Some(c) = self { c.add_leading(pos, cmt) @@ -419,8 +435,22 @@ pub struct SingleThreadedComments { } impl Comments for SingleThreadedComments { + fn reserve_comments(&self, leading: usize, trailing: usize) { + if leading != 0 { + self.leading.borrow_mut().reserve(leading); + } + + if trailing != 0 { + self.trailing.borrow_mut().reserve(trailing); + } + } + fn add_leading(&self, pos: BytePos, cmt: Comment) { - self.leading.borrow_mut().entry(pos).or_default().push(cmt); + self.leading + .borrow_mut() + .entry(pos) + .or_insert_with(|| Vec::with_capacity(1)) + .push(cmt); } fn add_leading_comments(&self, pos: BytePos, comments: Vec) { @@ -460,7 +490,11 @@ impl Comments for SingleThreadedComments { } fn add_trailing(&self, pos: BytePos, cmt: Comment) { - self.trailing.borrow_mut().entry(pos).or_default().push(cmt); + self.trailing + .borrow_mut() + .entry(pos) + .or_insert_with(|| Vec::with_capacity(1)) + .push(cmt); } fn add_trailing_comments(&self, pos: BytePos, comments: Vec) { diff --git a/crates/swc_common/src/input.rs b/crates/swc_common/src/input.rs index 50331f9a87ba..2dc6e92f1d65 100644 --- a/crates/swc_common/src/input.rs +++ b/crates/swc_common/src/input.rs @@ -89,29 +89,29 @@ impl<'a> From<&'a SourceFile> for StringInput<'a> { } impl<'a> Input<'a> for StringInput<'a> { - #[inline] + #[inline(always)] fn cur(&self) -> Option { self.remaining.as_bytes().first().copied() } - #[inline] + #[inline(always)] fn peek(&self) -> Option { self.remaining.as_bytes().get(1).copied() } - #[inline] + #[inline(always)] fn peek_ahead(&self) -> Option { self.remaining.as_bytes().get(2).copied() } - #[inline] + #[inline(always)] unsafe fn bump_bytes(&mut self, n: usize) { debug_assert!(n <= self.remaining.len()); self.remaining = unsafe { self.remaining.get_unchecked(n..) }; self.last_pos.0 += n as u32; } - #[inline] + #[inline(always)] fn cur_as_ascii(&self) -> Option { let first_byte = *self.remaining.as_bytes().first()?; if first_byte <= 0x7f { @@ -121,28 +121,28 @@ impl<'a> Input<'a> for StringInput<'a> { } } - #[inline] + #[inline(always)] fn cur_as_char(&self) -> Option { self.remaining.chars().next() } - #[inline] + #[inline(always)] fn is_at_start(&self) -> bool { self.orig_start == self.last_pos } /// TODO(kdy1): Remove this? - #[inline] + #[inline(always)] fn cur_pos(&self) -> BytePos { self.last_pos } - #[inline] + #[inline(always)] fn last_pos(&self) -> BytePos { self.last_pos } - #[inline] + #[inline(always)] unsafe fn slice(&mut self, start: BytePos, end: BytePos) -> &'a str { debug_assert!(start <= end, "Cannot slice {start:?}..{end:?}"); let s = self.orig; @@ -186,7 +186,7 @@ impl<'a> Input<'a> for StringInput<'a> { ret } - #[inline] + #[inline(always)] unsafe fn reset_to(&mut self, to: BytePos) { if self.last_pos == to { // No need to reset. @@ -201,7 +201,7 @@ impl<'a> Input<'a> for StringInput<'a> { self.last_pos = to; } - #[inline] + #[inline(always)] fn is_byte(&self, c: u8) -> bool { self.remaining .as_bytes() @@ -210,12 +210,12 @@ impl<'a> Input<'a> for StringInput<'a> { .unwrap_or(false) } - #[inline] + #[inline(always)] fn is_str(&self, s: &str) -> bool { self.remaining.starts_with(s) } - #[inline] + #[inline(always)] unsafe fn eat_byte(&mut self, c: u8) -> bool { if self.is_byte(c) { self.remaining = unsafe { self.remaining.get_unchecked(1..) }; diff --git a/crates/swc_ecma_parser/src/lexer/comments_buffer.rs b/crates/swc_ecma_parser/src/lexer/comments_buffer.rs index 9331dcc99417..40c7f533f7ea 100644 --- a/crates/swc_ecma_parser/src/lexer/comments_buffer.rs +++ b/crates/swc_ecma_parser/src/lexer/comments_buffer.rs @@ -17,35 +17,49 @@ pub enum BufferedCommentKind { pub struct CommentsBuffer { comments: Vec, pending_leading: Vec, + leading_count: usize, + trailing_count: usize, } #[derive(Debug, Default)] pub struct CommentsBufferCheckpoint { comments_pos: usize, pending_leading: usize, + leading_count: usize, + trailing_count: usize, } impl CommentsBuffer { - pub fn new() -> Self { - Self::default() + pub fn with_capacity(comments: usize) -> Self { + Self { + comments: Vec::with_capacity(comments), + pending_leading: Vec::with_capacity(4), + leading_count: 0, + trailing_count: 0, + } } pub fn checkpoint_save(&self) -> CommentsBufferCheckpoint { CommentsBufferCheckpoint { comments_pos: self.comments.len(), pending_leading: self.pending_leading.len(), + leading_count: self.leading_count, + trailing_count: self.trailing_count, } } pub fn checkpoint_load(&mut self, checkpoint: CommentsBufferCheckpoint) { self.comments.truncate(checkpoint.comments_pos); self.pending_leading.truncate(checkpoint.pending_leading); + self.leading_count = checkpoint.leading_count; + self.trailing_count = checkpoint.trailing_count; } } impl CommentsBuffer { #[inline(always)] pub fn push_comment(&mut self, comment: BufferedComment) { + self.increment_count(comment.kind, 1); self.comments.push(comment); } @@ -54,11 +68,6 @@ impl CommentsBuffer { self.pending_leading.push(comment); } - #[inline(always)] - pub fn has_pending(&self) -> bool { - !self.pending_leading.is_empty() - } - #[inline(always)] pub fn pending_to_comment(&mut self, kind: BufferedCommentKind, pos: BytePos) { // Most tokens have no pending comments; avoid creating an empty drain on @@ -68,10 +77,13 @@ impl CommentsBuffer { 1 => { let comment = self.pending_leading.pop().unwrap(); let comment = BufferedComment { kind, pos, comment }; + self.increment_count(kind, 1); self.comments.push(comment); return; } - _ => {} + len => { + self.increment_count(kind, len); + } } for comment in self.pending_leading.drain(..) { @@ -80,8 +92,23 @@ impl CommentsBuffer { } } + #[inline(always)] + pub fn comment_counts(&self) -> (usize, usize) { + (self.leading_count, self.trailing_count) + } + #[inline(always)] pub fn take_comments(&mut self) -> impl Iterator + '_ { + self.leading_count = 0; + self.trailing_count = 0; self.comments.drain(..) } + + #[inline(always)] + fn increment_count(&mut self, kind: BufferedCommentKind, count: usize) { + match kind { + BufferedCommentKind::Leading => self.leading_count += count, + BufferedCommentKind::Trailing => self.trailing_count += count, + } + } } diff --git a/crates/swc_ecma_parser/src/lexer/mod.rs b/crates/swc_ecma_parser/src/lexer/mod.rs index e5df34d1c504..5ade4376e4d2 100644 --- a/crates/swc_ecma_parser/src/lexer/mod.rs +++ b/crates/swc_ecma_parser/src/lexer/mod.rs @@ -309,9 +309,13 @@ impl<'a> Lexer<'a> { } } + let comments_buffer = comments + .is_some() + .then(|| CommentsBuffer::with_capacity(input.as_str().len() / 64)); + Lexer { comments, - comments_buffer: comments.is_some().then(CommentsBuffer::new), + comments_buffer, ctx: Default::default(), input, start_pos, @@ -791,6 +795,7 @@ impl<'a> Lexer<'a> { if is_for_next { self.comments_buffer_mut().unwrap().push_pending(cmt); + self.state.has_pending_comments = true; } else { let pos = self.state.prev_hi; self.comments_buffer_mut().unwrap().push_comment(BufferedComment { @@ -822,6 +827,7 @@ impl<'a> Lexer<'a> { if is_for_next { self.comments_buffer_mut().unwrap().push_pending(cmt); + self.state.has_pending_comments = true; } else { let pos = self.state.prev_hi; self.comments_buffer_mut() @@ -931,6 +937,7 @@ impl<'a> Lexer<'a> { if is_for_next { self.comments_buffer_mut().unwrap().push_pending(cmt); + self.state.has_pending_comments = true; } else { let pos = self.state.prev_hi; self.comments_buffer_mut() @@ -1349,6 +1356,8 @@ impl<'a> Lexer<'a> { comments_buffer.pending_to_comment(kind, last); // now fill the user's passed in comments + let (leading, trailing) = comments_buffer.comment_counts(); + comments.reserve_comments(leading, trailing); for comment in comments_buffer.take_comments() { match comment.kind { BufferedCommentKind::Leading => { @@ -1359,6 +1368,7 @@ impl<'a> Lexer<'a> { } } } + self.state.has_pending_comments = false; } } diff --git a/crates/swc_ecma_parser/src/lexer/state.rs b/crates/swc_ecma_parser/src/lexer/state.rs index 5ec5eb0973dc..58c466c0ef82 100644 --- a/crates/swc_ecma_parser/src/lexer/state.rs +++ b/crates/swc_ecma_parser/src/lexer/state.rs @@ -37,6 +37,7 @@ static JSX_CHILD_TABLE: SafeByteMatchTable = pub struct State { /// if line break exists between previous token and new token? pub had_line_break: bool, + pub has_pending_comments: bool, pub next_regexp: Option, pub prev_hi: BytePos, @@ -409,7 +410,9 @@ impl Lexer<'_> { return self.read_regexp(next_regexp); } - self.state.had_line_break = false; + if self.state.had_line_break { + self.state.had_line_break = false; + } self.skip_space(); *start = self.input.cur_pos(); @@ -418,17 +421,16 @@ impl Lexer<'_> { #[inline(always)] fn finish_next_token(&mut self, span: Span, token: Token) -> TokenAndSpan { - if self.comments_buffer.is_some() { - if token == Token::Eof { + if token == Token::Eof { + if self.comments_buffer.is_some() { self.consume_pending_comments(); - } else { - let Some(comments_buffer) = self.comments_buffer.as_mut() else { - unreachable!(); - }; - if comments_buffer.has_pending() { - comments_buffer.pending_to_comment(BufferedCommentKind::Leading, span.lo); - } } + } else if self.state.has_pending_comments { + let Some(comments_buffer) = self.comments_buffer.as_mut() else { + unreachable!(); + }; + comments_buffer.pending_to_comment(BufferedCommentKind::Leading, span.lo); + self.state.has_pending_comments = false; } self.state.set_token_type(token); @@ -677,6 +679,7 @@ impl State { pub fn new(start_pos: BytePos) -> Self { State { had_line_break: false, + has_pending_comments: false, next_regexp: None, prev_hi: start_pos, token_value: None, diff --git a/crates/swc_ecma_parser/src/lexer/token.rs b/crates/swc_ecma_parser/src/lexer/token.rs index d19cb13b2b07..16e323c31205 100644 --- a/crates/swc_ecma_parser/src/lexer/token.rs +++ b/crates/swc_ecma_parser/src/lexer/token.rs @@ -383,16 +383,106 @@ impl<'a> Token { return word.clone(); } - let span = buffer.cur.span; - let atom = Atom::new(buffer.iter.read_string(span)); - atom + self.known_word_atom() + .unwrap_or_else(|| Atom::new(buffer.iter.read_string(buffer.cur.span))) } #[inline(always)] pub fn take_known_ident(self, buffer: &Buffer) -> Atom { - let span = buffer.cur.span; - let atom = Atom::new(buffer.iter.read_string(span)); - atom + self.known_word_atom() + .unwrap_or_else(|| Atom::new(buffer.iter.read_string(buffer.cur.span))) + } + + #[inline(always)] + fn known_word_atom(self) -> Option { + Some(match self { + Token::Await => swc_atoms::atom!("await"), + Token::Break => swc_atoms::atom!("break"), + Token::Case => swc_atoms::atom!("case"), + Token::Catch => swc_atoms::atom!("catch"), + Token::Class => swc_atoms::atom!("class"), + Token::Const => swc_atoms::atom!("const"), + Token::Continue => swc_atoms::atom!("continue"), + Token::Debugger => swc_atoms::atom!("debugger"), + Token::Default => swc_atoms::atom!("default"), + Token::Delete => swc_atoms::atom!("delete"), + Token::Do => swc_atoms::atom!("do"), + Token::Else => swc_atoms::atom!("else"), + Token::Export => swc_atoms::atom!("export"), + Token::Extends => swc_atoms::atom!("extends"), + Token::False => swc_atoms::atom!("false"), + Token::Finally => swc_atoms::atom!("finally"), + Token::For => swc_atoms::atom!("for"), + Token::Function => swc_atoms::atom!("function"), + Token::If => swc_atoms::atom!("if"), + Token::Import => swc_atoms::atom!("import"), + Token::In => swc_atoms::atom!("in"), + Token::InstanceOf => swc_atoms::atom!("instanceof"), + Token::Let => swc_atoms::atom!("let"), + Token::New => swc_atoms::atom!("new"), + Token::Null => swc_atoms::atom!("null"), + Token::Return => swc_atoms::atom!("return"), + Token::Super => swc_atoms::atom!("super"), + Token::Switch => swc_atoms::atom!("switch"), + Token::This => swc_atoms::atom!("this"), + Token::Throw => swc_atoms::atom!("throw"), + Token::True => swc_atoms::atom!("true"), + Token::Try => swc_atoms::atom!("try"), + Token::TypeOf => swc_atoms::atom!("typeof"), + Token::Var => swc_atoms::atom!("var"), + Token::Void => swc_atoms::atom!("void"), + Token::While => swc_atoms::atom!("while"), + Token::With => swc_atoms::atom!("with"), + Token::Yield => swc_atoms::atom!("yield"), + Token::Module => swc_atoms::atom!("module"), + Token::Abstract => swc_atoms::atom!("abstract"), + Token::Any => swc_atoms::atom!("any"), + Token::As => swc_atoms::atom!("as"), + Token::Asserts => swc_atoms::atom!("asserts"), + Token::Assert => swc_atoms::atom!("assert"), + Token::Async => swc_atoms::atom!("async"), + Token::Bigint => swc_atoms::atom!("bigint"), + Token::Boolean => swc_atoms::atom!("boolean"), + Token::Constructor => swc_atoms::atom!("constructor"), + Token::Declare => swc_atoms::atom!("declare"), + Token::Enum => swc_atoms::atom!("enum"), + Token::From => swc_atoms::atom!("from"), + Token::Get => swc_atoms::atom!("get"), + Token::Global => swc_atoms::atom!("global"), + Token::Implements => swc_atoms::atom!("implements"), + Token::Interface => swc_atoms::atom!("interface"), + Token::Intrinsic => swc_atoms::atom!("intrinsic"), + Token::Is => swc_atoms::atom!("is"), + Token::Keyof => swc_atoms::atom!("keyof"), + Token::Namespace => swc_atoms::atom!("namespace"), + Token::Never => swc_atoms::atom!("never"), + Token::Number => swc_atoms::atom!("number"), + Token::Object => swc_atoms::atom!("object"), + Token::Of => swc_atoms::atom!("of"), + Token::Out => swc_atoms::atom!("out"), + Token::Override => swc_atoms::atom!("override"), + Token::Package => swc_atoms::atom!("package"), + Token::Private => swc_atoms::atom!("private"), + Token::Protected => swc_atoms::atom!("protected"), + Token::Public => swc_atoms::atom!("public"), + Token::Readonly => swc_atoms::atom!("readonly"), + Token::Require => swc_atoms::atom!("require"), + Token::Set => swc_atoms::atom!("set"), + Token::Static => swc_atoms::atom!("static"), + Token::String => swc_atoms::atom!("string"), + Token::Symbol => swc_atoms::atom!("symbol"), + Token::Type => swc_atoms::atom!("type"), + Token::Undefined => swc_atoms::atom!("undefined"), + Token::Unique => swc_atoms::atom!("unique"), + Token::Unknown => swc_atoms::atom!("unknown"), + Token::Using => swc_atoms::atom!("using"), + Token::Accessor => swc_atoms::atom!("accessor"), + Token::Infer => swc_atoms::atom!("infer"), + Token::Satisfies => swc_atoms::atom!("satisfies"), + Token::Meta => swc_atoms::atom!("meta"), + Token::Target => swc_atoms::atom!("target"), + _ => return None, + }) } #[inline(always)] @@ -527,13 +617,20 @@ impl Token { #[inline(always)] pub const fn needs_unary_expr_prefix_parse(self) -> bool { - let t = self as u8; - (t >= Token::Bang as u8 && t <= Token::Minus as u8) - || (t >= Token::PlusPlus as u8 && t <= Token::MinusMinus as u8) - || matches!( - self, - Token::Lt | Token::Await | Token::Delete | Token::TypeOf | Token::Void - ) + matches!( + self, + Token::Bang + | Token::Tilde + | Token::Plus + | Token::Minus + | Token::PlusPlus + | Token::MinusMinus + | Token::Lt + | Token::Await + | Token::Delete + | Token::TypeOf + | Token::Void + ) } pub const fn as_bin_op(self) -> Option { diff --git a/crates/swc_ecma_parser/src/parser/expr.rs b/crates/swc_ecma_parser/src/parser/expr.rs index f8625d28d8ee..fb477ed0ebad 100644 --- a/crates/swc_ecma_parser/src/parser/expr.rs +++ b/crates/swc_ecma_parser/src/parser/expr.rs @@ -188,7 +188,7 @@ impl Parser { return Ok(cond); } - match *cond { + match &*cond { // if cond is conditional expression but not left-hand-side expression, // just return it. Expr::Cond(..) | Expr::Bin(..) | Expr::Unary(..) | Expr::Update(..) => return Ok(cond), @@ -338,7 +338,7 @@ impl Parser { // UpdateExpression let expr = self.parse_lhs_expr()?; - if let Expr::Arrow { .. } = *expr { + if let Expr::Arrow { .. } = &*expr { return Ok(expr); } @@ -378,6 +378,18 @@ impl Parser { let tok = self.input.cur(); match tok { Token::This => return self.parse_this_expr(start), + Token::Ident if !self.input().syntax().flow_pattern_matching() => { + let word = self.input_mut().expect_word_token_value(); + self.bump(); + if self.ctx().contains(Context::InClassField) && word == atom!("arguments") { + self.emit_err(self.input().prev_span(), SyntaxError::ArgumentsInClassField) + }; + let id = Ident::new_no_ctxt(word, self.span(start)); + if !can_be_arrow || self.input().cur() != Token::Arrow { + return Ok(id.into()); + } + return self.parse_arrow_expr_from_ident(start, id, false); + } Token::Async => { if let Some(res) = self.try_parse_async_start(can_be_arrow) { return res; @@ -430,18 +442,22 @@ impl Parser { let cur = self.input().cur(); - // `super()` can't be handled from parse_new_expr() - if cur == Token::Super { - let start = self.input().cur_pos(); - self.bump(); // eat `super` - let obj = Callee::Super(Super { - span: self.span(start), - }); - return self.parse_subscripts(obj, false, false); - } else if cur == Token::Import { - let start = self.input().cur_pos(); - self.bump(); // eat `import` - return self.parse_dynamic_import_or_import_meta(start, false); + match cur { + // `super()` can't be handled from parse_new_expr() + Token::Super => { + let start = self.input().cur_pos(); + self.bump(); // eat `super` + let obj = Callee::Super(Super { + span: self.span(start), + }); + return self.parse_subscripts(obj, false, false); + } + Token::Import => { + let start = self.input().cur_pos(); + self.bump(); // eat `import` + return self.parse_dynamic_import_or_import_meta(start, false); + } + _ => {} } let callee = self.parse_new_expr()?; @@ -464,7 +480,7 @@ impl Parser { None }; - if let Expr::New(ne @ NewExpr { args: None, .. }) = *callee { + if let Expr::New(NewExpr { args: None, .. }) = &*callee { // If this is parsed using 'NewExpression' rule, just return it. // Because it's not left-recursive. if type_args.is_some() { @@ -475,6 +491,9 @@ impl Parser { self.input().cur() != Token::LParen, "parse_new_expr() should eat paren if it exists" ); + let Expr::New(ne) = *callee else { + unreachable!() + }; return Ok(NewExpr { type_args, ..ne }.into()); } // 'CallExpr' rule contains 'MemberExpr (...)', @@ -1154,11 +1173,14 @@ impl Parser { no_call: bool, ) -> PResult> { let cur = self.input().cur(); - if !matches!( + if cur == Token::LParen { + if no_call { + return Ok(expr); + } + } else if !matches!( cur, Token::QuestionMark | Token::LBracket - | Token::LParen | Token::Dot | Token::TemplateHead | Token::NoSubstitutionTemplateLiteral @@ -2888,129 +2910,145 @@ impl Parser { } } - fn parse_primary_expr_rest( + #[inline(never)] + fn parse_arrow_expr_from_ident( &mut self, start: BytePos, - can_be_arrow: bool, + id: Ident, + id_is_async: bool, ) -> PResult> { - let decorators = if self.input().is(Token::At) { - Some(self.parse_decorators(false)?) - } else { - None - }; - let cur = self.input().cur(); - if self.is_flow_match_keyword() { - return self.parse_flow_match_expr(start); - } - - if cur == Token::Class { - return self.parse_class_expr(start, decorators.unwrap_or_default()); - } - - let try_parse_arrow_expr = |p: &mut Self, id: Ident, id_is_async| -> PResult> { - let cur = p.input().cur(); - - if id_is_async && cur.is_word() && !cur.is_reserved(p.ctx()) { - if !p.input().had_line_break_before_cur() { - // see https://github.com/tc39/ecma262/issues/2034 - // ```js - // for(async of - // for(async of x); - // for(async of =>{};;); + if id_is_async && cur.is_word() && !cur.is_reserved(self.ctx()) { + if !self.input().had_line_break_before_cur() { + // see https://github.com/tc39/ecma262/issues/2034 + // ```js + // for(async of + // for(async of x); + // for(async of =>{};;); + // ``` + let ctx = self.ctx(); + if ctx.contains(Context::ForLoopInit) + && self.input().is(Token::Of) + && !peek!(self).is_some_and(|peek| peek == Token::Arrow) + { + // ```spec https://tc39.es/ecma262/#prod-ForInOfStatement + // for ( [lookahead not in { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] + // [+Await] for await ( [lookahead != let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] // ``` - let ctx = p.ctx(); - if ctx.contains(Context::ForLoopInit) - && p.input().is(Token::Of) - && !peek!(p).is_some_and(|peek| peek == Token::Arrow) - { - // ```spec https://tc39.es/ecma262/#prod-ForInOfStatement - // for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] - // [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] - // ``` - if !ctx.contains(Context::ForAwaitLoopInit) { - p.emit_err(p.input().prev_span(), SyntaxError::TS1106); - } - - return Ok(id.into()); - } - - let token = cur; - let ident = p.parse_binding_ident(false)?; - if p.input().syntax().typescript() - && token == Token::As - && !p.input().is(Token::Arrow) - { - // async as type - let type_ann = p.in_type(Self::parse_ts_type)?; - return Ok(TsAsExpr { - span: p.span(start), - expr: Box::new(id.into()), - type_ann, - } - .into()); + if !ctx.contains(Context::ForAwaitLoopInit) { + self.emit_err(self.input().prev_span(), SyntaxError::TS1106); } - // async a => body - let arg = ident.into(); - let params = vec![arg]; - expect!(p, Token::Arrow); - let body = p.parse_fn_block_or_expr_body( - true, - false, - true, - params.is_simple_parameter_list(), - )?; + return Ok(id.into()); + } - return Ok(ArrowExpr { - span: p.span(start), - body, - params, - is_async: true, - is_generator: false, - ..Default::default() + let token = cur; + let ident = self.parse_binding_ident(false)?; + if self.input().syntax().typescript() + && token == Token::As + && !self.input().is(Token::Arrow) + { + // async as type + let type_ann = self.in_type(Self::parse_ts_type)?; + return Ok(TsAsExpr { + span: self.span(start), + expr: Box::new(id.into()), + type_ann, } .into()); } - } else if cur == Token::Arrow && !p.input().had_line_break_before_cur() { - if p.ctx().contains(Context::Strict) && id.is_reserved_in_strict_bind() { - p.emit_strict_mode_err(id.span, SyntaxError::EvalAndArgumentsInStrict) - } - let params = vec![id.into()]; - p.bump(); - let body = p.parse_fn_block_or_expr_body( - false, + + // async a => body + let arg = ident.into(); + let params = vec![arg]; + expect!(self, Token::Arrow); + let body = self.parse_fn_block_or_expr_body( + true, false, true, params.is_simple_parameter_list(), )?; return Ok(ArrowExpr { - span: p.span(start), + span: self.span(start), body, params, - is_async: false, + is_async: true, is_generator: false, ..Default::default() } .into()); } + } else if cur == Token::Arrow && !self.input().had_line_break_before_cur() { + if self.ctx().contains(Context::Strict) && id.is_reserved_in_strict_bind() { + self.emit_strict_mode_err(id.span, SyntaxError::EvalAndArgumentsInStrict) + } + let params = vec![id.into()]; + self.bump(); + let body = self.parse_fn_block_or_expr_body( + false, + false, + true, + params.is_simple_parameter_list(), + )?; - Ok(id.into()) - }; + return Ok(ArrowExpr { + span: self.span(start), + body, + params, + is_async: false, + is_generator: false, + ..Default::default() + } + .into()); + } - if cur == Token::Let || (cur == Token::Await && self.input().syntax().typescript()) { + Ok(id.into()) + } + + fn parse_primary_expr_rest( + &mut self, + start: BytePos, + can_be_arrow: bool, + ) -> PResult> { + let mut cur = self.input().cur(); + if cur == Token::At { + let decorators = self.parse_decorators(false)?; + cur = self.input().cur(); + if cur == Token::Class { + return self.parse_class_expr(start, decorators); + } + } + + if self.input().syntax().flow_pattern_matching() && self.is_flow_match_keyword() { + return self.parse_flow_match_expr(start); + } + + if cur == Token::Class { + self.parse_class_expr(start, Vec::new()) + } else if cur == Token::Ident { + let word = self.input_mut().expect_word_token_value(); + self.bump(); + if self.ctx().contains(Context::InClassField) && word == atom!("arguments") { + self.emit_err(self.input().prev_span(), SyntaxError::ArgumentsInClassField) + }; + let id = Ident::new_no_ctxt(word, self.span(start)); + if !can_be_arrow || self.input().cur() != Token::Arrow { + return Ok(id.into()); + } + self.parse_arrow_expr_from_ident(start, id, false) + } else if cur == Token::Let || (cur == Token::Await && self.input().syntax().typescript()) { let ctx = self.ctx(); let id = self.parse_ident( !ctx.contains(Context::InGenerator), !ctx.contains(Context::InAsync), )?; - if !can_be_arrow { + if !can_be_arrow || self.input().cur() != Token::Arrow { return Ok(id.into()); } - try_parse_arrow_expr(self, id, false) + self.parse_arrow_expr_from_ident(start, id, false) } else if cur == Token::Hash { self.bump(); // consume `#` let id = self.parse_ident_name()?; @@ -3022,24 +3060,20 @@ impl Parser { name: id.sym, } .into()) - } else if cur == Token::Ident { - let word = self.input_mut().expect_word_token_and_bump(); - if self.ctx().contains(Context::InClassField) && word == atom!("arguments") { - self.emit_err(self.input().prev_span(), SyntaxError::ArgumentsInClassField) - }; - let id = Ident::new_no_ctxt(word, self.span(start)); - if !can_be_arrow { - return Ok(id.into()); - } - try_parse_arrow_expr(self, id, false) } else if self.is_ident_ref() { let cur = self.input().cur(); - let word = self.input_mut().expect_word_token_and_bump(); + let word = cur.take_word(&self.input); + self.bump(); let id = Ident::new_no_ctxt(word, self.span(start)); - if !can_be_arrow { + let id_is_async = cur == Token::Async; + let next = self.input().cur(); + if !can_be_arrow || (!id_is_async && next != Token::Arrow) { + return Ok(id.into()); + } + if id_is_async && next != Token::Arrow && !next.is_word() { return Ok(id.into()); } - try_parse_arrow_expr(self, id, cur == Token::Async) + self.parse_arrow_expr_from_ident(start, id, id_is_async) } else { syntax_error!(self, self.input().cur_span(), SyntaxError::TS1109) } diff --git a/crates/swc_ecma_parser/src/parser/ident.rs b/crates/swc_ecma_parser/src/parser/ident.rs index 198e865f91e2..2a30f54d6a56 100644 --- a/crates/swc_ecma_parser/src/parser/ident.rs +++ b/crates/swc_ecma_parser/src/parser/ident.rs @@ -25,8 +25,14 @@ impl Parser { let token_and_span = self.input().get_cur(); let start = token_and_span.span.lo; let cur = token_and_span.token; - let w = if cur.is_word() { - self.input_mut().expect_word_token_and_bump() + let w = if cur == Token::Ident { + let word = self.input_mut().expect_word_token_value(); + self.bump(); + word + } else if cur.is_word() { + let word = cur.take_word(&self.input); + self.bump(); + word } else if cur == Token::JSXName && self.ctx().contains(Context::InType) { self.input_mut().expect_jsx_name_token_and_bump() } else { @@ -89,7 +95,8 @@ impl Parser { unexpected!(self, "let is reserved in const, let, class declaration") } else if cur == Token::Ident { let span = self.input().cur_span(); - let word = self.input_mut().expect_word_token_and_bump(); + let word = self.input_mut().expect_word_token_value(); + self.bump(); if atom!("arguments") == word || atom!("eval") == word { self.emit_strict_mode_err(span, SyntaxError::EvalAndArgumentsInStrict); } @@ -147,7 +154,8 @@ impl Parser { // StringValue of IdentifierName is: "implements", "interface", "let", // "package", "private", "protected", "public", "static", or "yield". if t == Token::Enum { - let word = self.input_mut().expect_word_token_and_bump(); + let word = t.take_word(&self.input); + self.bump(); self.emit_err(span, SyntaxError::InvalidIdentInStrict(word.clone())); return Ok(Ident::new_no_ctxt(word, self.span(start))); } else if t == Token::Yield @@ -160,7 +168,8 @@ impl Parser { || t == Token::Protected || t == Token::Public { - let word = self.input_mut().expect_word_token_and_bump(); + let word = t.take_word(&self.input); + self.bump(); self.emit_strict_mode_err(span, SyntaxError::InvalidIdentInStrict(word.clone())); return Ok(Ident::new_no_ctxt(word, self.span(start))); }; @@ -193,7 +202,8 @@ impl Parser { let ident = t.take_known_ident(&self.input); word = ident } else if t == Token::Ident { - let word = self.input_mut().expect_word_token_and_bump(); + let word = self.input_mut().expect_word_token_value(); + self.bump(); if self.ctx().contains(Context::InClassField) && word == atom!("arguments") { self.emit_err(span, SyntaxError::ArgumentsInClassField) } diff --git a/crates/swc_ecma_parser/src/parser/input.rs b/crates/swc_ecma_parser/src/parser/input.rs index ffeb0078b4f8..290fa124d04f 100644 --- a/crates/swc_ecma_parser/src/parser/input.rs +++ b/crates/swc_ecma_parser/src/parser/input.rs @@ -95,6 +95,7 @@ pub struct Buffer { } impl Buffer { + #[inline(always)] pub fn expect_word_token_value(&mut self) -> Atom { let Some(crate::lexer::TokenValue::Word(word)) = self.iter.take_token_value() else { unreachable!() @@ -102,6 +103,7 @@ impl Buffer { word } + #[inline(always)] pub fn expect_word_token_value_ref(&self) -> &Atom { let Some(crate::lexer::TokenValue::Word(word)) = self.iter.get_token_value() else { unreachable!("token_value: {:?}", self.iter.get_token_value()) @@ -317,6 +319,7 @@ impl Buffer { self.set_cur(next); } + #[inline(always)] pub fn expect_word_token_and_bump(&mut self) -> Atom { let cur = self.cur(); let word = if cur == Token::Ident {