/* * Copyright (c) 2026-present, the Ladybird developers. * * SPDX-License-Identifier: BSD-2-Clause */ //! Expression parsing: primary, secondary (binary/postfix), unary, and //! precedence climbing. use std::sync::Arc; use crate::ast::*; use crate::lexer::ch; use crate::parser::Associativity; use crate::parser::ForbiddenTokens; use crate::parser::FunctionKind; use crate::parser::MethodKind; use crate::parser::PRECEDENCE_ASSIGNMENT; use crate::parser::PRECEDENCE_COMMA; use crate::parser::PRECEDENCE_MEMBER; use crate::parser::PRECEDENCE_UNARY; use crate::parser::ParamInfo; use crate::parser::ParsedParameters; use crate::parser::Parser; use crate::parser::Position; use crate::parser::PropertyKey; use crate::parser::is_strict_reserved_word; use crate::token::Token; use crate::token::TokenType; #[derive(PartialEq, Eq)] enum EscapeMode { /// Tagged template literals: invalid escapes produce `undefined` cooked value. TaggedTemplate, /// String literals: invalid escapes emit syntax errors, legacy octals are tracked. StringLiteral, } impl Parser<'_> { pub(crate) fn match_expression(&mut self) -> bool { match self.current_token_type() { TokenType::BoolLiteral | TokenType::NumericLiteral | TokenType::BigIntLiteral | TokenType::StringLiteral | TokenType::NullLiteral | TokenType::RegexLiteral | TokenType::TemplateLiteralStart | TokenType::This | TokenType::Super | TokenType::New | TokenType::Class | TokenType::Function | TokenType::ParenOpen | TokenType::CurlyOpen | TokenType::BracketOpen | TokenType::PrivateIdentifier | TokenType::Slash | TokenType::SlashEquals => true, TokenType::Async => true, TokenType::Yield => true, TokenType::Await => true, // https://tc39.es/ecma262/#sec-import-calls // https://tc39.es/ecma262/#sec-import-meta // `import` is only a valid expression start if followed by `(` or `.`. TokenType::Import => { let next = self.next_token(); next.token_type == TokenType::ParenOpen || next.token_type == TokenType::Period } _ => { if self.match_identifier() { return true; } self.match_unary_prefixed_expression() } } } pub(crate) fn match_unary_prefixed_expression(&self) -> bool { matches!( self.current_token_type(), TokenType::PlusPlus | TokenType::MinusMinus | TokenType::ExclamationMark | TokenType::Tilde | TokenType::Plus | TokenType::Minus | TokenType::Typeof | TokenType::Void | TokenType::Delete ) || (self.flags.await_expression_is_valid && self.current_token_type() == TokenType::Await) } pub(crate) fn match_secondary_expression(&self, forbidden: &ForbiddenTokens) -> bool { let tt = self.current_token_type(); if !forbidden.allows(tt) { return false; } match tt { TokenType::Period | TokenType::BracketOpen | TokenType::ParenOpen | TokenType::QuestionMarkPeriod => true, TokenType::PlusPlus | TokenType::MinusMinus => !self.current_token.trivia_has_line_terminator, TokenType::DoubleAsterisk | TokenType::Asterisk | TokenType::Slash | TokenType::Percent | TokenType::Plus | TokenType::Minus | TokenType::ShiftLeft | TokenType::ShiftRight | TokenType::UnsignedShiftRight | TokenType::LessThan | TokenType::LessThanEquals | TokenType::GreaterThan | TokenType::GreaterThanEquals | TokenType::In | TokenType::Instanceof | TokenType::EqualsEquals | TokenType::ExclamationMarkEquals | TokenType::EqualsEqualsEquals | TokenType::ExclamationMarkEqualsEquals | TokenType::Ampersand | TokenType::Caret | TokenType::Pipe | TokenType::DoubleQuestionMark | TokenType::DoubleAmpersand | TokenType::DoublePipe => true, TokenType::QuestionMark => true, TokenType::Equals | TokenType::PlusEquals | TokenType::MinusEquals | TokenType::DoubleAsteriskEquals | TokenType::AsteriskEquals | TokenType::SlashEquals | TokenType::PercentEquals | TokenType::ShiftLeftEquals | TokenType::ShiftRightEquals | TokenType::UnsignedShiftRightEquals | TokenType::AmpersandEquals | TokenType::CaretEquals | TokenType::PipeEquals | TokenType::DoubleAmpersandEquals | TokenType::DoublePipeEquals | TokenType::DoubleQuestionMarkEquals => true, _ => false, } } pub(crate) fn parse_expression_any(&mut self) -> Expression { self.parse_expression(PRECEDENCE_COMMA, Associativity::Right, ForbiddenTokens::none()) } pub(crate) fn parse_assignment_expression(&mut self) -> Expression { self.parse_expression(PRECEDENCE_ASSIGNMENT, Associativity::Right, ForbiddenTokens::none()) } pub(crate) fn parse_expression( &mut self, min_precedence: i32, associativity: Associativity, forbidden: ForbiddenTokens, ) -> Expression { if self.match_unary_prefixed_expression() { let start = self.position(); let expression = self.parse_unary_prefixed_expression(); // https://tc39.es/ecma262/#sec-exp-operator // ExponentiationExpression : // UnaryExpression // UpdateExpression `**` ExponentiationExpression // NB: UnaryExpression cannot be the base of `**`, only UpdateExpression can. // This prevents ambiguity like `-x ** y` (is it `(-x) ** y` or `-(x ** y)`?). // ++x ** y and --x ** y are valid (they're UpdateExpressions, not UnaryExpressions). if self.match_token(TokenType::DoubleAsterisk) && !Self::is_update_expression(&expression) { self.syntax_error("Unparenthesized unary expression can't appear on the left-hand side of '**'"); } return self.continue_parse_expression(start, expression, min_precedence, associativity, forbidden); } let lhs_start = self.position(); self.last_primary_was_parenthesized = false; let (expression, should_continue) = self.parse_primary_expression(min_precedence, forbidden); // C++ checks for freestanding `arguments` references here (after // parse_primary_expression), NOT during consume(). This avoids // falsely flagging parameter names like `function f(arguments)`. if let ExpressionKind::Identifier(id) = expression.inner && self.arena.name_of(id).as_slice() == utf16!("arguments") { // https://tc39.es/ecma262/#sec-class-static-initialization-blocks // It is a Syntax Error if ContainsArguments of ClassStaticBlockBody is true. if self.flags.in_class_static_init_block { self.syntax_error("'arguments' is not allowed in class static initialization blocks"); } else { let name = self.arena.name_of(id).clone(); if !self.flags.strict_mode && !self.scope_collector.has_declaration_in_current_function(&name) { self.scope_collector .set_contains_access_to_arguments_object_in_non_strict_mode(); } } } if !should_continue { // Yield/Await expressions don't participate in secondary expression // parsing (e.g. member access), but they DO participate in comma // expressions (e.g. `yield 1, yield 2`). Check for comma here. let expression = self.parse_comma_expression(lhs_start, expression, min_precedence, forbidden); self.report_invalid_private_identifier_usage(&expression); return expression; } let expression = self.parse_tagged_template_literals(lhs_start, expression); self.continue_parse_expression(lhs_start, expression, min_precedence, associativity, forbidden) } fn continue_parse_expression( &mut self, lhs_start: Position, mut expression: Expression, min_precedence: i32, associativity: Associativity, mut forbidden: ForbiddenTokens, ) -> Expression { let original_forbidden = forbidden; let mut lhs_is_parenthesized = self.last_primary_was_parenthesized; self.last_primary_was_parenthesized = false; while self.match_secondary_expression(&forbidden) { let new_precedence = Self::operator_precedence(self.current_token_type()); if new_precedence < min_precedence { break; } if new_precedence == min_precedence && associativity == Associativity::Left { break; } let result = self.parse_secondary_expression( lhs_start, expression, new_precedence, original_forbidden, lhs_is_parenthesized, ); lhs_is_parenthesized = false; expression = result.0; forbidden = forbidden.merge(result.1); // Tagged template literals bind tighter than any operator, so we // consume them eagerly after each secondary expression — but NOT // after update expressions (x++`template` is not valid). if !Self::is_update_expression(&expression) { expression = self.parse_tagged_template_literals(lhs_start, expression); } } let expression = self.parse_comma_expression(lhs_start, expression, min_precedence, forbidden); self.report_invalid_private_identifier_usage(&expression); expression } fn parse_comma_expression( &mut self, start: Position, expression: Expression, min_precedence: i32, forbidden: ForbiddenTokens, ) -> Expression { if min_precedence <= 1 && self.match_token(TokenType::Comma) && forbidden.allows(TokenType::Comma) { self.report_invalid_private_identifier_usage(&expression); let mut expressions = vec![expression]; while self.match_token(TokenType::Comma) { self.consume(); let expression = self.parse_assignment_expression(); self.report_invalid_private_identifier_usage(&expression); expressions.push(expression); } return self.expression(start, ExpressionKind::Sequence(Box::new(expressions))); } expression } fn report_invalid_private_identifier_usage(&mut self, expression: &Expression) { if matches!(expression.inner, ExpressionKind::PrivateIdentifier(_)) { self.syntax_error("Private identifier must be followed by 'in'"); } } /// Parse a primary expression (literal, identifier, `this`, etc.). /// Returns `(expression, should_continue)` — `false` means the caller /// should not attempt to parse a secondary expression (e.g. arrow). fn parse_primary_expression(&mut self, min_precedence: i32, forbidden: ForbiddenTokens) -> (Expression, bool) { let start = self.position(); let token = self.current_token().clone(); match token.token_type { TokenType::ParenOpen => { let paren_start = self.position(); self.consume_token(TokenType::ParenOpen); if let Some(arrow) = self.try_parse_arrow_function_expression(true, false, Some(paren_start), forbidden) { return (arrow, false); } if self.match_token(TokenType::ParenClose) { self.syntax_error("Unexpected token )"); self.consume(); return (self.expression(start, ExpressionKind::Error), true); } let mut expression = self.parse_expression_any(); self.consume_token(TokenType::ParenClose); if let ExpressionKind::New(ref mut new_expression) = expression.inner { // Mirrors C++ Parser: `(new Foo)` sets "inside grouping parens". new_expression.is_inside_parens = true; } self.last_primary_was_parenthesized = true; (expression, true) } TokenType::This => { self.consume(); self.scope_collector.set_uses_this(); (self.expression(start, ExpressionKind::This), true) } TokenType::Class => { let expression = self.parse_class_expression(false); (expression, true) } // https://tc39.es/ecma262/#sec-super-keyword // SuperProperty : `super` `.` IdentifierName // | `super` `[` Expression `]` // SuperCall : `super` Arguments // NB: `super` must be followed by `.`, `[`, or `(`; bare `super` is invalid. TokenType::Super => { self.consume(); // C++ creates SuperCall in parse_call_expression which does // push_start() after `super` is consumed, so position is at `(`. let after_super = self.position(); if self.scope_collector.has_current_scope() { self.scope_collector.set_uses_new_target(); } if self.match_token(TokenType::ParenOpen) { if !self.flags.allow_super_constructor_call { self.syntax_error("'super' keyword unexpected here"); } let arguments = self.parse_arguments(); ( self.expression( after_super, ExpressionKind::SuperCall(Box::new(SuperCallData { arguments, is_synthetic: false, })), ), true, ) } else if self.match_token(TokenType::Period) || self.match_token(TokenType::BracketOpen) { if !self.flags.allow_super_property_lookup { self.syntax_error("'super' keyword unexpected here"); } (self.expression(start, ExpressionKind::Super), true) } else { self.syntax_error("'super' keyword unexpected here"); (self.expression(start, ExpressionKind::Super), true) } } TokenType::NumericLiteral => { let token = self.consume_and_validate_numeric_literal(); let value_str = self.token_value(&token); let value = parse_numeric_value(value_str); (self.expression(start, ExpressionKind::NumericLiteral(value)), true) } TokenType::BigIntLiteral => { let token = self.consume(); let value = self.token_value(&token); // Store the raw value including the 'n' suffix, matching C++. let value = Utf16String(value.to_vec()); ( self.expression(start, ExpressionKind::BigIntLiteral(Box::new(value))), true, ) } TokenType::BoolLiteral => { let token = self.consume(); let value = self.token_value(&token); let is_true = value == utf16!("true"); (self.expression(start, ExpressionKind::BooleanLiteral(is_true)), true) } TokenType::StringLiteral => { let token = self.consume(); // C++ calls consume() before push_start() for StringLiteral, // so its position is the token AFTER the string. let after_string = self.position(); let (value, has_octal) = self.parse_string_value(&token); if has_octal { if self.flags.strict_mode { self.syntax_error("Octal escape sequence in string literal not allowed in strict mode"); } else { self.flags.string_legacy_octal_escape_sequence_in_scope = true; } } ( self.expression(after_string, ExpressionKind::StringLiteral(Box::new(value))), true, ) } TokenType::NullLiteral => { self.consume(); (self.expression(start, ExpressionKind::NullLiteral), true) } TokenType::CurlyOpen => { let expression = self.parse_object_expression(); (expression, true) } TokenType::BracketOpen => { let expression = self.parse_array_expression(); (expression, true) } TokenType::Function => { let expression = self.parse_function_expression(); (expression, true) } // https://tc39.es/ecma262/#sec-async-function-definitions // `async` [no LineTerminator here] `function` starts an async function expression. // `async` [no LineTerminator here] ArrowParameters `=>` starts an async arrow. // Otherwise, `async` is just an identifier reference. TokenType::Async => { let next = self.next_token(); if next.token_type == TokenType::Function && !next.trivia_has_line_terminator { let expression = self.parse_function_expression(); return (expression, true); } if let Some(arrow) = self.try_parse_arrow_function_expression( next.token_type == TokenType::ParenOpen, true, None, forbidden, ) { return (arrow, false); } self.arrow_function_failed_positions.remove(&(start.offset as usize)); // `async => ...` is a regular arrow function with parameter name `async` // (not an async arrow function). if let Some(arrow) = self.try_parse_arrow_function_expression(false, false, None, forbidden) { return (arrow, false); } let token = self.consume_and_check_identifier(); let name = self.token_identifier_name(&token); let id = self.make_identifier(start, name); let Self { scope_collector, arena, .. } = self; scope_collector.register_identifier( id, None, &mut arena.identifiers, &arena.strings, &mut arena.scopes, ); (self.expression(start, ExpressionKind::Identifier(id)), true) } TokenType::TemplateLiteralStart => { let expression = self.parse_template_literal(false); (expression, true) } TokenType::New => { let expression = self.parse_new_expression(); (expression, true) } // https://tc39.es/ecma262/#sec-import-calls // ImportCall : `import` `(` AssignmentExpression `,`? `) // | `import` `(` AssignmentExpression `,` AssignmentExpression `,`? `)` // https://tc39.es/ecma262/#sec-import-meta // `import.meta` is only valid in module code. TokenType::Import => { self.consume(); if self.match_token(TokenType::Period) { self.consume(); let meta_token = self.current_token.clone(); self.consume_token(TokenType::Identifier); let meta_utf16: [u16; 4] = [ch(b'm'), ch(b'e'), ch(b't'), ch(b'a')]; if self.token_original_value(&meta_token) != meta_utf16 { self.syntax_error("Expected 'meta' after 'import.'"); } if self.program_type != ProgramType::Module { self.syntax_error("import.meta is only allowed in modules"); } ( self.expression(start, ExpressionKind::MetaProperty(MetaPropertyType::ImportMeta)), true, ) } else if self.match_token(TokenType::ParenOpen) { self.consume(); let specifier = self.parse_assignment_expression(); let options = if self.match_token(TokenType::Comma) { self.consume(); if self.match_token(TokenType::ParenClose) { None } else { let opts = self.parse_assignment_expression(); if self.match_token(TokenType::Comma) { self.consume(); } Some(Box::new(opts)) } } else { None }; self.consume_token(TokenType::ParenClose); ( self.expression( start, ExpressionKind::ImportCall(Box::new(ImportCallData { specifier: Box::new(specifier), options, })), ), true, ) } else { self.expected("'.' or '('"); (self.expression(start, ExpressionKind::Error), true) } } // https://tc39.es/ecma262/#sec-generator-function-definitions // YieldExpression : `yield` // | `yield` [no LineTerminator here] AssignmentExpression // | `yield` [no LineTerminator here] `*` AssignmentExpression // YieldExpression is at AssignmentExpression level (precedence 3). // When min_precedence is higher (e.g. void/typeof at 17), yield must // be treated as an identifier, not a yield expression. TokenType::Yield if self.flags.in_generator_function_context && min_precedence <= 3 => { let expression = self.parse_yield_expression(forbidden); (expression, false) } TokenType::PrivateIdentifier => { let id = self.parse_private_identifier(start); ( self.expression(start, ExpressionKind::PrivateIdentifier(Box::new(id))), true, ) } TokenType::RegexLiteral => { let token = self.consume(); (self.parse_regex_literal(start, &token), true) } TokenType::Slash | TokenType::SlashEquals => { let token = self.lexer.force_slash_as_regex(); self.current_token = token; let token = self.consume(); (self.parse_regex_literal(start, &token), true) } _ => { // NB: When Await/Yield guards above don't match, those tokens fall // through here. match_identifier() may return false for Await in // class static init blocks, but we still need to try arrow function // parsing and identifier consumption (with appropriate errors). // This matches C++'s "goto read_as_identifier" pattern. if self.match_identifier() || self.match_token(TokenType::Await) || self.match_token(TokenType::Yield) { if let Some(arrow) = self.try_parse_arrow_function_expression(false, false, None, forbidden) { return (arrow, false); } if self.match_token(TokenType::Await) && (self.program_type == ProgramType::Module || self.flags.await_expression_is_valid || self.flags.in_class_static_init_block) { self.syntax_error("'await' is not allowed as an identifier in this context"); } if self.match_token(TokenType::Yield) && (self.flags.strict_mode || self.flags.in_generator_function_context) { self.syntax_error("'yield' is not allowed as an identifier in this context"); } let token = self.consume_and_check_identifier(); let name = self.token_identifier_name(&token); let id = self.make_identifier(start, name); let Self { scope_collector, arena, .. } = self; scope_collector.register_identifier( id, None, &mut arena.identifiers, &arena.strings, &mut arena.scopes, ); (self.expression(start, ExpressionKind::Identifier(id)), true) } else if self.match_token(TokenType::EscapedKeyword) { self.syntax_error("Keyword must not contain escaped characters"); let token = self.consume_and_check_identifier(); let name = self.token_identifier_name(&token); let id = self.make_identifier(start, name); let Self { scope_collector, arena, .. } = self; scope_collector.register_identifier( id, None, &mut arena.identifiers, &arena.strings, &mut arena.scopes, ); (self.expression(start, ExpressionKind::Identifier(id)), true) } else { self.expected("primary expression"); self.consume(); (self.expression(start, ExpressionKind::Error), true) } } } } fn parse_regex_literal(&mut self, start: Position, token: &Token) -> Expression { let value = self.token_value(token); let pattern = if value.len() >= 2 { value[1..value.len() - 1].to_vec() } else { value.to_vec() }; let flags = if self.match_token(TokenType::RegexFlags) { let ftok = self.consume(); self.token_value(&ftok).to_vec() } else { Vec::new() }; self.validate_regex_flags(&flags); let compiled_regex = match crate::bytecode::ffi::compile_regex(&pattern, &flags) { Ok(handle) => Arc::new(CompiledRegex::new(handle)), Err(msg) => { self.syntax_error_at_position(&String::from_utf16_lossy(&msg), start); Arc::new(CompiledRegex::new(std::ptr::null_mut())) } }; self.expression( start, ExpressionKind::RegExpLiteral(Box::new(RegExpLiteralData { pattern: pattern.into(), flags: flags.into(), compiled_regex, })), ) } fn parse_secondary_expression( &mut self, _lhs_start: Position, lhs: Expression, min_precedence: i32, forbidden: ForbiddenTokens, lhs_is_parenthesized: bool, ) -> (Expression, ForbiddenTokens) { let start = self.position(); let tt = self.current_token_type(); if matches!(lhs.inner, ExpressionKind::PrivateIdentifier(_)) && tt != TokenType::In { self.syntax_error("Private identifier must be followed by 'in'"); } match tt { // === Binary operators === TokenType::Plus | TokenType::Minus | TokenType::Asterisk | TokenType::Slash | TokenType::Percent | TokenType::DoubleAsterisk | TokenType::ShiftLeft | TokenType::ShiftRight | TokenType::UnsignedShiftRight | TokenType::Ampersand | TokenType::Caret | TokenType::Pipe | TokenType::LessThan | TokenType::LessThanEquals | TokenType::GreaterThan | TokenType::GreaterThanEquals | TokenType::EqualsEquals | TokenType::ExclamationMarkEquals | TokenType::EqualsEqualsEquals | TokenType::ExclamationMarkEqualsEquals | TokenType::Instanceof => { let op = token_to_binary_op(tt); self.consume(); let rhs = self.parse_expression(min_precedence, Self::operator_associativity(tt), forbidden); ( self.expression( start, ExpressionKind::Binary(Box::new(BinaryExprData { op, lhs: Box::new(lhs), rhs: Box::new(rhs), })), ), ForbiddenTokens::none(), ) } TokenType::In => { let is_private_in = matches!(&lhs.inner, ExpressionKind::PrivateIdentifier(_)); self.consume(); let rhs = self.parse_expression(min_precedence, Self::operator_associativity(tt), forbidden); if is_private_in && let ExpressionKind::Function(fn_id) = &rhs.inner && self.function_table.get(*fn_id).is_arrow_function { self.syntax_error( "Arrow function is not allowed as the right-hand side of a private 'in' expression", ); } ( self.expression( start, ExpressionKind::Binary(Box::new(BinaryExprData { op: BinaryOp::In, lhs: Box::new(lhs), rhs: Box::new(rhs), })), ), ForbiddenTokens::none(), ) } // === Logical operators === // https://tc39.es/ecma262/#sec-binary-logical-operators // It is a Syntax Error if ShortCircuitExpression includes both // LogicalORExpression (||/&&) and CoalesceExpression (??), since // their precedence is ambiguous without explicit parentheses. TokenType::DoubleAmpersand => { self.consume(); let new_forbidden = forbidden.forbid(&[TokenType::DoubleQuestionMark]); let rhs = self.parse_expression(min_precedence, Associativity::Left, new_forbidden); ( self.expression( start, ExpressionKind::Logical(Box::new(LogicalExprData { op: LogicalOp::And, lhs: Box::new(lhs), rhs: Box::new(rhs), })), ), new_forbidden, ) } TokenType::DoublePipe => { self.consume(); let new_forbidden = forbidden.forbid(&[TokenType::DoubleQuestionMark]); let rhs = self.parse_expression(min_precedence, Associativity::Left, new_forbidden); ( self.expression( start, ExpressionKind::Logical(Box::new(LogicalExprData { op: LogicalOp::Or, lhs: Box::new(lhs), rhs: Box::new(rhs), })), ), new_forbidden, ) } TokenType::DoubleQuestionMark => { self.consume(); let new_forbidden = forbidden.forbid(&[TokenType::DoubleAmpersand, TokenType::DoublePipe]); let rhs = self.parse_expression(min_precedence, Associativity::Left, new_forbidden); ( self.expression( start, ExpressionKind::Logical(Box::new(LogicalExprData { op: LogicalOp::NullishCoalescing, lhs: Box::new(lhs), rhs: Box::new(rhs), })), ), new_forbidden, ) } // === Assignment === TokenType::Equals | TokenType::PlusEquals | TokenType::MinusEquals | TokenType::DoubleAsteriskEquals | TokenType::AsteriskEquals | TokenType::SlashEquals | TokenType::PercentEquals | TokenType::ShiftLeftEquals | TokenType::ShiftRightEquals | TokenType::UnsignedShiftRightEquals | TokenType::AmpersandEquals | TokenType::CaretEquals | TokenType::PipeEquals | TokenType::DoubleAmpersandEquals | TokenType::DoublePipeEquals | TokenType::DoubleQuestionMarkEquals => { let op = token_to_assignment_op(tt); if op == AssignmentOp::Assignment && !lhs_is_parenthesized && (Self::is_object_expression(&lhs) || Self::is_array_expression(&lhs)) { // Save pattern_bound_names so that an outer binding // pattern parse in progress doesn't lose its entries. let saved_bound_names = std::mem::take(&mut self.pattern_bound_names); // Use the expression's own range start, not the outer // lhs_start. When the expression is parenthesized (e.g. // `([a,b]) = ...`), lhs_start points to `(` but we need // to re-lex from `[` to correctly synthesize the pattern. let binding_pattern = self.synthesize_binding_pattern(lhs.range.start); // Register synthesized identifiers with the scope collector so // they get resolved as locals during analyze(). let bound_names: Vec<_> = self.pattern_bound_names.drain(..).collect(); for (name, _id) in &bound_names { let name_str = self.arena.strings[*name].clone(); self.check_identifier_name_for_assignment_validity(name_str.as_slice(), false); } let Self { scope_collector, arena, .. } = self; for (_name, id) in &bound_names { scope_collector.register_identifier( *id, None, &mut arena.identifiers, &arena.strings, &mut arena.scopes, ); } self.pattern_bound_names = saved_bound_names; self.consume(); let rhs = self.parse_expression(min_precedence, Associativity::Right, forbidden); return ( self.expression( start, ExpressionKind::Assignment(Box::new(AssignmentExprData { op, lhs: AssignmentLhs::Pattern(binding_pattern), rhs: Box::new(rhs), lhs_is_parenthesized, })), ), ForbiddenTokens::none(), ); } let allow_call = !matches!( tt, TokenType::DoubleAmpersandEquals | TokenType::DoublePipeEquals | TokenType::DoubleQuestionMarkEquals ); if !Self::is_simple_assignment_target(&lhs, allow_call, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in assignment"); } if let ExpressionKind::Identifier(id) = lhs.inner { let name = self.arena.name_of(id).clone(); self.check_identifier_name_for_assignment_validity(&name, false); } self.consume(); let rhs = self.parse_expression(min_precedence, Associativity::Right, forbidden); ( self.expression( start, ExpressionKind::Assignment(Box::new(AssignmentExprData { op, lhs: AssignmentLhs::Expression(Box::new(lhs)), rhs: Box::new(rhs), lhs_is_parenthesized, })), ), ForbiddenTokens::none(), ) } // === Ternary === TokenType::QuestionMark => { self.consume(); let consequent = self.parse_assignment_expression(); self.consume_token(TokenType::Colon); let alternate = self.parse_expression(PRECEDENCE_ASSIGNMENT, Associativity::Right, forbidden); ( self.expression( start, ExpressionKind::Conditional(Box::new(ConditionalExprData { test: Box::new(lhs), consequent: Box::new(consequent), alternate: Box::new(alternate), })), ), ForbiddenTokens::none(), ) } // === Member access === TokenType::Period => { self.consume(); if self.match_token(TokenType::PrivateIdentifier) { // https://tc39.es/ecma262/#sec-static-semantics-early-errors // It is a Syntax Error if MemberExpression is SuperProperty // and the PrivateIdentifier is present. if matches!(lhs.inner, ExpressionKind::Super) { self.syntax_error("Cannot access private field or method via 'super'"); } let property_start = self.position(); let id = self.parse_private_identifier(property_start); let property = self.expression(property_start, ExpressionKind::PrivateIdentifier(Box::new(id))); ( self.expression( start, ExpressionKind::Member(Box::new(MemberExprData { object: Box::new(lhs), property: Box::new(property), computed: false, })), ), ForbiddenTokens::none(), ) } else if self.match_identifier_name() { let property_start = self.position(); let token = self.consume(); let property_name = self.token_identifier_name(&token); let property_identifier = self.make_identifier(property_start, property_name); let property = self.expression(property_start, ExpressionKind::Identifier(property_identifier)); ( self.expression( start, ExpressionKind::Member(Box::new(MemberExprData { object: Box::new(lhs), property: Box::new(property), computed: false, })), ), ForbiddenTokens::none(), ) } else { self.expected("property name"); (lhs, ForbiddenTokens::none()) } } // === Computed member access === TokenType::BracketOpen => { self.consume(); let property = self.parse_expression_any(); self.consume_token(TokenType::BracketClose); ( self.expression( start, ExpressionKind::Member(Box::new(MemberExprData { object: Box::new(lhs), property: Box::new(property), computed: true, })), ), ForbiddenTokens::none(), ) } // === Call === TokenType::ParenOpen => { let expression = self.parse_call_expression(lhs); (expression, ForbiddenTokens::none()) } // === Optional chaining === TokenType::QuestionMarkPeriod => { // https://tc39.es/ecma262/#prod-OptionalExpression // Optional chaining directly on an unparenthesized `new` expression is invalid: // `new Foo?.bar` // SyntaxError // while parenthesized/new-call forms remain valid: // `(new Foo)?.bar` // `new Foo()?.bar` if let ExpressionKind::New(ref new_expression) = lhs.inner && !new_expression.is_parenthesized && !new_expression.is_inside_parens { self.syntax_error("'new' cannot be used with optional chaining"); self.consume(); return (lhs, ForbiddenTokens::none()); } let chain = self.parse_optional_chain(start, lhs); (chain, ForbiddenTokens::none()) } // === Postfix === // https://tc39.es/ecma262/#sec-update-expressions // UpdateExpression : LeftHandSideExpression [no LineTerminator here] `++` // | LeftHandSideExpression [no LineTerminator here] `--` // NB: The [no LineTerminator here] is enforced by match_secondary_expression // which checks trivia_has_line_terminator for PlusPlus/MinusMinus. TokenType::PlusPlus => { if !Self::is_simple_assignment_target(&lhs, true, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in postfix operation"); } if let ExpressionKind::Identifier(id) = lhs.inner { let name = self.arena.name_of(id).clone(); self.check_identifier_name_for_assignment_validity(&name, false); } self.consume(); ( self.expression( start, ExpressionKind::Update(Box::new(UpdateExprData { op: UpdateOp::Increment, argument: Box::new(lhs), prefixed: false, })), ), ForbiddenTokens::none(), ) } TokenType::MinusMinus => { if !Self::is_simple_assignment_target(&lhs, true, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in postfix operation"); } if let ExpressionKind::Identifier(id) = lhs.inner { let name = self.arena.name_of(id).clone(); self.check_identifier_name_for_assignment_validity(&name, false); } self.consume(); ( self.expression( start, ExpressionKind::Update(Box::new(UpdateExprData { op: UpdateOp::Decrement, argument: Box::new(lhs), prefixed: false, })), ), ForbiddenTokens::none(), ) } _ => { self.expected("secondary expression"); (lhs, ForbiddenTokens::none()) } } } fn parse_unary_prefixed_expression(&mut self) -> Expression { let start = self.position(); let tt = self.current_token_type(); match tt { TokenType::Await if self.flags.await_expression_is_valid => self.parse_await_expression(), TokenType::PlusPlus => { self.consume(); let expression = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none()); if !Self::is_simple_assignment_target(&expression, true, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in prefix operation"); } if let ExpressionKind::Identifier(id) = expression.inner { let name = self.arena.name_of(id).clone(); self.check_identifier_name_for_assignment_validity(&name, false); } self.expression( start, ExpressionKind::Update(Box::new(UpdateExprData { op: UpdateOp::Increment, argument: Box::new(expression), prefixed: true, })), ) } TokenType::MinusMinus => { self.consume(); let expression = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none()); if !Self::is_simple_assignment_target(&expression, true, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in prefix operation"); } if let ExpressionKind::Identifier(id) = expression.inner { let name = self.arena.name_of(id).clone(); self.check_identifier_name_for_assignment_validity(&name, false); } self.expression( start, ExpressionKind::Update(Box::new(UpdateExprData { op: UpdateOp::Decrement, argument: Box::new(expression), prefixed: true, })), ) } TokenType::ExclamationMark | TokenType::Tilde | TokenType::Plus | TokenType::Minus | TokenType::Typeof | TokenType::Void => { let op = match tt { TokenType::ExclamationMark => UnaryOp::Not, TokenType::Tilde => UnaryOp::BitwiseNot, TokenType::Plus => UnaryOp::Plus, TokenType::Minus => UnaryOp::Minus, TokenType::Typeof => UnaryOp::Typeof, _ => UnaryOp::Void, }; self.consume(); let expression = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none()); self.report_invalid_private_identifier_usage(&expression); self.expression( start, ExpressionKind::Unary { op, operand: Box::new(expression), }, ) } // https://tc39.es/ecma262/#sec-delete-operator-static-semantics-early-errors // It is a Syntax Error if the UnaryExpression is an IdentifierReference // and the source text matched by the enclosing Script or Module is strict mode code. TokenType::Delete => { self.consume(); let rhs_start = self.position(); let expression = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none()); self.report_invalid_private_identifier_usage(&expression); if self.flags.strict_mode && Self::is_identifier(&expression) { self.syntax_error_at( "Delete of an unqualified identifier in strict mode.", rhs_start.line, rhs_start.column, ); } if let ExpressionKind::Member(ref data) = expression.inner && matches!(data.property.inner, ExpressionKind::PrivateIdentifier(_)) { self.syntax_error("Private fields cannot be deleted"); } self.expression( start, ExpressionKind::Unary { op: UnaryOp::Delete, operand: Box::new(expression), }, ) } _ => { self.expected("unary expression"); self.consume(); self.expression(start, ExpressionKind::Error) } } } /// Parse a `new` expression, handling `new.target` and nested `new` calls. // https://tc39.es/ecma262/#sec-new-operator // MemberExpression : `new` MemberExpression Arguments // NewExpression : `new` NewExpression // https://tc39.es/ecma262/#sec-meta-properties // NewTarget : `new` `.` `target` fn parse_new_expression(&mut self) -> Expression { let start = self.position(); self.consume_token(TokenType::New); if self.match_token(TokenType::Period) { self.consume(); let target_token = self.current_token.clone(); self.consume_token(TokenType::Identifier); if self.token_original_value(&target_token) != utf16!("target") { self.syntax_error("Expected 'target' after 'new.'"); } // https://tc39.es/ecma262/#sec-new.target // It is a Syntax Error if NewTarget is not enclosed, directly or indirectly // (but not crossing function or class static initialization block boundaries), // within a FunctionBody, ConciseBody, ClassStaticBlock, or ClassBody. if !self.flags.new_target_is_valid && !self.in_eval_function_context { self.syntax_error("'new.target' not allowed outside of a function"); } if self.scope_collector.has_current_scope() { self.scope_collector.set_uses_new_target(); } return self.expression(start, ExpressionKind::MetaProperty(MetaPropertyType::NewTarget)); } let callee_starts_with_grouping_paren = self.match_token(TokenType::ParenOpen); let callee = if self.match_token(TokenType::New) { self.parse_new_expression() } else { let forbidden = ForbiddenTokens::none().forbid(&[TokenType::ParenOpen, TokenType::QuestionMarkPeriod]); self.parse_expression(PRECEDENCE_MEMBER, Associativity::Right, forbidden) }; if matches!(callee.inner, ExpressionKind::ImportCall(_)) && !callee_starts_with_grouping_paren { self.syntax_error("Cannot call new on dynamic import"); } if self.match_token(TokenType::ParenOpen) { let arguments = self.parse_arguments(); self.expression( start, ExpressionKind::New(Box::new(CallExpressionData { callee: Box::new(callee), arguments, // Mirrors C++ InvocationStyle::Parenthesized for `new Foo(...)`. is_parenthesized: true, is_inside_parens: false, })), ) } else { self.expression( start, ExpressionKind::New(Box::new(CallExpressionData { callee: Box::new(callee), arguments: Vec::new(), is_parenthesized: false, is_inside_parens: false, })), ) } } /// Parse a call expression `callee(arguments...)`. // https://tc39.es/ecma262/#sec-function-calls // https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation // NB: A direct call to `eval` (bare identifier `eval` as callee) uses the // running execution context's variable environment, not a fresh one. pub(crate) fn parse_call_expression(&mut self, callee: Expression) -> Expression { let start = self.position(); let arguments = self.parse_arguments(); // Check the actual callee expression kind, matching C++ which does // is(callee) && callee.string() == "eval". if let ExpressionKind::Identifier(id) = callee.inner && self.arena.name_of(id).as_slice() == utf16!("eval") { self.scope_collector.set_contains_direct_call_to_eval(); self.scope_collector.set_uses_this(); } self.expression( start, ExpressionKind::Call(Box::new(CallExpressionData { callee: Box::new(callee), arguments, is_parenthesized: false, is_inside_parens: false, })), ) } pub(crate) fn parse_arguments(&mut self) -> Vec { self.consume_token(TokenType::ParenOpen); let mut arguments = Vec::new(); while !self.match_token(TokenType::ParenClose) && !self.done() { let is_spread = self.eat(TokenType::TripleDot); let value = self.parse_assignment_expression(); arguments.push(CallArgument { value, is_spread }); if !self.match_token(TokenType::Comma) { break; } self.consume(); } self.consume_token(TokenType::ParenClose); arguments } /// Parse an optional chaining expression (`a?.b`, `a?.[x]`, `a?.()`). // https://tc39.es/ecma262/#sec-optional-chains // OptionalExpression : MemberExpression OptionalChain // OptionalChain : `?.` Arguments // | `?.` `[` Expression `]` // | `?.` IdentifierName // | `?.` TemplateLiteral -- NOTE: this is a syntax error (see below) // | OptionalChain Arguments // | OptionalChain `[` Expression `]` // | OptionalChain `.` IdentifierName // | OptionalChain TemplateLiteral -- also a syntax error fn parse_optional_chain(&mut self, start: Position, base: Expression) -> Expression { let mut references = Vec::new(); loop { if self.match_token(TokenType::QuestionMarkPeriod) { self.consume(); match self.current_token_type() { TokenType::ParenOpen => { let arguments = self.parse_arguments(); references.push(OptionalChainReference::Call { arguments, mode: OptionalChainMode::Optional, }); } TokenType::BracketOpen => { self.consume(); let expression = self.parse_expression_any(); self.consume_token(TokenType::BracketClose); references.push(OptionalChainReference::ComputedReference { expression: Box::new(expression), mode: OptionalChainMode::Optional, }); } TokenType::PrivateIdentifier => { let property_start = self.position(); let id = self.parse_private_identifier(property_start); references.push(OptionalChainReference::PrivateMemberReference { private_identifier: id, mode: OptionalChainMode::Optional, }); } // https://tc39.es/ecma262/#sec-optional-chaining-chain-production // It is a Syntax Error if any code matches this production: // OptionalChain : `?.` TemplateLiteral // Tagged templates cannot be used with optional chaining. TokenType::TemplateLiteralStart => { self.syntax_error("Invalid tagged template literal after ?."); break; } _ => { if self.match_identifier_name() { let property_start = self.position(); let token = self.consume(); let name = self.token_identifier_name(&token); references.push(OptionalChainReference::MemberReference { identifier: self.make_identifier(property_start, name), mode: OptionalChainMode::Optional, }); } else { self.syntax_error("Invalid optional chain reference after ?."); break; } } } } else if self.match_token(TokenType::ParenOpen) { let arguments = self.parse_arguments(); references.push(OptionalChainReference::Call { arguments, mode: OptionalChainMode::NotOptional, }); } else if self.match_token(TokenType::Period) { self.consume(); if self.match_token(TokenType::PrivateIdentifier) { let property_start = self.position(); let id = self.parse_private_identifier(property_start); references.push(OptionalChainReference::PrivateMemberReference { private_identifier: id, mode: OptionalChainMode::NotOptional, }); } else if self.match_identifier_name() { let property_start = self.position(); let token = self.consume(); let name = self.token_identifier_name(&token); references.push(OptionalChainReference::MemberReference { identifier: self.make_identifier(property_start, name), mode: OptionalChainMode::NotOptional, }); } else { self.expected("an identifier"); break; } } else if self.match_token(TokenType::TemplateLiteralStart) { self.syntax_error("Invalid tagged template literal after optional chain"); break; } else if self.match_token(TokenType::BracketOpen) { self.consume(); let expression = self.parse_expression_any(); self.consume_token(TokenType::BracketClose); references.push(OptionalChainReference::ComputedReference { expression: Box::new(expression), mode: OptionalChainMode::NotOptional, }); } else { break; } if self.done() { break; } } self.expression( start, ExpressionKind::OptionalChain(Box::new(OptionalChainData { base: Box::new(base), references, })), ) } /// Parse a `yield` or `yield*` expression. // https://tc39.es/ecma262/#sec-generator-function-definitions // YieldExpression : `yield` // | `yield` [no LineTerminator here] AssignmentExpression // | `yield` [no LineTerminator here] `*` AssignmentExpression // https://tc39.es/ecma262/#sec-generator-function-definitions-static-semantics-early-errors // It is a Syntax Error if YieldExpression appears within FormalParameters. fn parse_yield_expression(&mut self, forbidden: ForbiddenTokens) -> Expression { let start = self.position(); if self.flags.in_formal_parameter_context { self.syntax_error("'Yield' expression is not allowed in formal parameters of generator function"); } self.consume_token(TokenType::Yield); if self.current_token.trivia_has_line_terminator { return self.expression( start, ExpressionKind::Yield(Box::new(YieldExprData { argument: None, is_yield_from: false, })), ); } let is_yield_from = self.match_token(TokenType::Asterisk); if is_yield_from { self.consume(); } if is_yield_from || self.match_expression() || self.match_token(TokenType::Class) { // https://tc39.es/ecma262/#prod-YieldExpression // YieldExpression[In, Await] : // `yield` [no LineTerminator here] AssignmentExpression[?In, +Yield, ?Await] // `yield` [no LineTerminator here] `*` AssignmentExpression[?In, +Yield, ?Await] let argument = self.parse_expression(PRECEDENCE_ASSIGNMENT, Associativity::Right, forbidden); self.expression( start, ExpressionKind::Yield(Box::new(YieldExprData { argument: Some(Box::new(argument)), is_yield_from, })), ) } else { self.expression( start, ExpressionKind::Yield(Box::new(YieldExprData { argument: None, is_yield_from: false, })), ) } } /// Parse an `await` expression. // https://tc39.es/ecma262/#sec-async-function-definitions // AwaitExpression : `await` UnaryExpression // https://tc39.es/ecma262/#sec-async-function-definitions-static-semantics-early-errors // It is a Syntax Error if AwaitExpression appears within FormalParameters. fn parse_await_expression(&mut self) -> Expression { let start = self.position(); if self.flags.in_formal_parameter_context { self.syntax_error("'Await' expression is not allowed in formal parameters of an async function"); } self.consume_token(TokenType::Await); let argument = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none()); self.scope_collector.set_contains_await_expression(); self.expression(start, ExpressionKind::Await(Box::new(argument))) } fn parse_object_expression(&mut self) -> Expression { let start = self.position(); self.consume_token(TokenType::CurlyOpen); let mut properties = Vec::new(); let mut has_proto_setter = false; while !self.match_token(TokenType::CurlyClose) && !self.done() { if self.match_token(TokenType::TripleDot) { let spread_start = self.position(); self.consume(); let expression = self.parse_assignment_expression(); properties.push(ObjectProperty { range: self.range_from(spread_start), property_type: ObjectPropertyType::Spread, key: Box::new(expression), is_computed: false, value: None, is_method: false, }); } else { let property = self.parse_object_property(start); // https://tc39.es/ecma262/#sec-object-initializer-static-semantics-early-errors // It is a Syntax Error if PropertyNameList of PropertyDefinitionList // contains any duplicate entries for "__proto__" and at least two of // those entries were obtained from productions of the form // PropertyDefinition : PropertyName `:` AssignmentExpression. if property.property_type == ObjectPropertyType::ProtoSetter { if has_proto_setter { self.syntax_error("Duplicate __proto__ fields are not allowed in object expressions"); } has_proto_setter = true; } properties.push(property); } if !self.match_token(TokenType::Comma) { break; } self.consume(); } self.consume_token(TokenType::CurlyClose); self.expression(start, ExpressionKind::Object(Box::new(properties))) } fn parse_object_property(&mut self, obj_start: Position) -> ObjectProperty { let start = self.position(); let mut is_getter = false; let mut is_setter = false; let mut is_async = false; let mut is_generator = false; if self.match_identifier_name() { let value = self.token_original_value(&self.current_token).to_vec(); if value == utf16!("get") && self.match_property_key_ahead() { is_getter = true; self.consume(); } else if value == utf16!("set") && self.match_property_key_ahead() { is_setter = true; self.consume(); } else if value == utf16!("async") { let next = self.next_token(); if !next.trivia_has_line_terminator && next.token_type != TokenType::ParenOpen && next.token_type != TokenType::Colon && next.token_type != TokenType::Comma && next.token_type != TokenType::CurlyClose { is_async = true; self.consume(); if self.match_token(TokenType::Asterisk) { is_generator = true; self.consume(); } } } } if !is_getter && !is_setter && !is_async && self.match_token(TokenType::Asterisk) { is_generator = true; self.consume(); } let PropertyKey { expression: key, name: key_value, is_proto, is_computed, is_identifier, } = self.parse_property_key(); // https://tc39.es/ecma262/#sec-object-initializer // Private names are not allowed in object literals, even inside class bodies. if let ExpressionKind::PrivateIdentifier(_) = key.inner { self.syntax_error("Private field or method is not allowed in object literal"); } if self.match_token(TokenType::ParenOpen) { let method_kind = if is_getter { MethodKind::Getter } else if is_setter { MethodKind::Setter } else { MethodKind::Normal }; let function = self.parse_method_definition(is_async, is_generator, method_kind, start); let property_type = if is_getter { ObjectPropertyType::Getter } else if is_setter { ObjectPropertyType::Setter } else { ObjectPropertyType::KeyValue }; return ObjectProperty { range: self.range_from(obj_start), property_type, key: Box::new(key), value: Some(Box::new(function)), is_method: true, is_computed, }; } // async modifier requires a method (must have parens) if is_async { self.syntax_error("Expected function after async keyword"); } // Generator shorthand requires a method body. if is_generator { self.syntax_error("Expected method after generator star"); } if is_getter || is_setter { let method_kind = if is_getter { MethodKind::Getter } else { MethodKind::Setter }; let function = self.parse_method_definition(false, false, method_kind, start); let property_type = if is_getter { ObjectPropertyType::Getter } else { ObjectPropertyType::Setter }; return ObjectProperty { range: self.range_from(obj_start), property_type, key: Box::new(key), value: Some(Box::new(function)), is_method: true, is_computed, }; } if self.match_token(TokenType::Colon) { self.consume(); let value = self.parse_assignment_expression(); let property_type = if is_proto { ObjectPropertyType::ProtoSetter } else { ObjectPropertyType::KeyValue }; return ObjectProperty { range: self.range_from(obj_start), property_type, key: Box::new(key), value: Some(Box::new(value)), is_method: false, is_computed, }; } // https://tc39.es/ecma262/#sec-object-initializer // CoverInitializedName : IdentifierReference Initializer // https://tc39.es/ecma262/#sec-object-initializer-static-semantics-early-errors // It is a Syntax Error if PropertyDefinitionList contains any CoverInitializedName. // NB: This is not a valid object literal, but is a valid destructuring assignment // target. We parse the initializer to advance the lexer, but roll back scope records // since this expression is discarded. synthesize_binding_pattern will // re-parse from source and create the real scope records. if self.match_token(TokenType::Equals) && is_identifier && let Some(kv) = &key_value { let id = self.make_identifier_from_slice(obj_start, kv.as_slice()); { let Self { scope_collector, arena, .. } = self; scope_collector.register_identifier( id, None, &mut arena.identifiers, &arena.strings, &mut arena.scopes, ); } let value = self.expression(obj_start, ExpressionKind::Identifier(id)); self.consume(); // consume '=' // NB: Add a syntax error for CoverInitializedName. This error will // be cleared by synthesize_binding_pattern if the containing object // is reinterpreted as a destructuring pattern, but will persist if // the object is used in expression context (e.g. as a member base). self.syntax_error("Invalid property in object literal"); let saved_scope_state = self.scope_collector.save_state(); let _initializer = self.parse_assignment_expression(); self.scope_collector.load_state(saved_scope_state); return ObjectProperty { range: self.range_from(obj_start), property_type: ObjectPropertyType::KeyValue, key: Box::new(key), value: Some(Box::new(value)), is_method: false, is_computed: false, }; } // Shorthand property: { x } // Only identifiers can be shorthand properties, not string/numeric literals. if let Some(kv) = key_value.filter(|_| is_identifier) { // https://tc39.es/ecma262/#sec-object-initializer-static-semantics-early-errors // Strict-mode reserved words cannot be used as shorthand properties. if self.flags.strict_mode && is_strict_reserved_word(&kv) { let name_str = String::from_utf16_lossy(&kv); self.syntax_error(&format!("'{name_str}' is a reserved keyword")); } let id = self.make_identifier_from_slice(obj_start, kv.as_slice()); { let Self { scope_collector, arena, .. } = self; scope_collector.register_identifier( id, None, &mut arena.identifiers, &arena.strings, &mut arena.scopes, ); } let value = self.expression(obj_start, ExpressionKind::Identifier(id)); return ObjectProperty { range: self.range_from(obj_start), property_type: ObjectPropertyType::KeyValue, key: Box::new(key), value: Some(Box::new(value)), is_method: false, is_computed: false, }; } self.expected("':' or '('"); ObjectProperty { range: self.range_from(obj_start), property_type: ObjectPropertyType::KeyValue, key: Box::new(key), value: None, is_method: false, is_computed, } } pub(crate) fn match_property_key_ahead(&mut self) -> bool { let next = self.next_token(); matches!( next.token_type, TokenType::BracketOpen | TokenType::StringLiteral | TokenType::NumericLiteral | TokenType::BigIntLiteral | TokenType::PrivateIdentifier ) || next.token_type.is_identifier_name() } pub(crate) fn parse_property_key(&mut self) -> PropertyKey { let proto_name = utf16!("__proto__"); let start = self.position(); match self.current_token_type() { TokenType::BracketOpen => { self.consume(); let expression = self.parse_assignment_expression(); self.consume_token(TokenType::BracketClose); PropertyKey { expression, name: None, is_proto: false, is_computed: true, is_identifier: false, } } TokenType::StringLiteral => { let token = self.consume_property_key_token(); // C++ calls consume() before push_start() for StringLiteral, // so its position is the token AFTER the string. let after_string = self.position(); let (value, has_octal) = self.parse_string_value(&token); if has_octal { if self.flags.strict_mode { self.syntax_error("Octal escape sequence in string literal not allowed in strict mode"); } else { self.flags.string_legacy_octal_escape_sequence_in_scope = true; } } let is_proto = value == proto_name; let expression = self.expression(after_string, ExpressionKind::StringLiteral(Box::new(value.clone()))); PropertyKey { expression, name: Some(value), is_proto, is_computed: false, is_identifier: false, } } TokenType::NumericLiteral => { let token = self.consume_and_validate_numeric_literal(); let value_str = self.token_value(&token); let value = parse_numeric_value(value_str); let expression = self.expression(start, ExpressionKind::NumericLiteral(value)); PropertyKey { expression, name: None, is_proto: false, is_computed: false, is_identifier: false, } } TokenType::BigIntLiteral => { let token = self.consume(); let value = self.token_value(&token); // Store the raw value including the 'n' suffix, matching C++. let value = Utf16String(value.to_vec()); let expression = self.expression(start, ExpressionKind::BigIntLiteral(Box::new(value))); PropertyKey { expression, name: None, is_proto: false, is_computed: false, is_identifier: false, } } // https://tc39.es/ecma262/#sec-class-definitions-static-semantics-early-errors // It is a Syntax Error if the StringValue of PrivateIdentifier is "#constructor". TokenType::PrivateIdentifier => { let token = self.consume(); let value = Utf16String::from(self.token_value(&token)); if value == utf16!("#constructor") { self.syntax_error("Private property with name '#constructor' is not allowed"); } let expression = self.expression( start, ExpressionKind::PrivateIdentifier(Box::new(PrivateIdentifier { range: self.range_from(start), name: value.clone(), })), ); PropertyKey { expression, name: Some(value), is_proto: false, is_computed: false, is_identifier: false, } } _ => { if self.match_identifier_name() { // is_identifier is true only for valid identifier references (not just // identifier names). This matters for shorthand properties: { await } // is not valid in class static blocks since await is not an identifier there. let is_ident = self.match_identifier(); let token = self.consume_property_key_token(); let value = Utf16String::from(self.token_value(&token)); let is_proto = value == proto_name; let expression = self.expression(start, ExpressionKind::StringLiteral(Box::new(value.clone()))); PropertyKey { expression, name: Some(value), is_proto, is_computed: false, is_identifier: is_ident, } } else { self.expected("property key"); self.consume_property_key_token(); let expression = self.expression(start, ExpressionKind::StringLiteral(Box::new(Utf16String::new()))); PropertyKey { expression, name: None, is_proto: false, is_computed: false, is_identifier: false, } } } } } fn parse_array_expression(&mut self) -> Expression { let start = self.position(); self.consume_token(TokenType::BracketOpen); let mut elements: Vec> = Vec::new(); while !self.match_token(TokenType::BracketClose) && !self.done() { if self.match_token(TokenType::Comma) { elements.push(None); self.consume(); continue; } if self.match_token(TokenType::TripleDot) { let spread_start = self.position(); self.consume(); let expression = self.parse_assignment_expression(); elements.push(Some( self.expression(spread_start, ExpressionKind::Spread(Box::new(expression))), )); } else { elements.push(Some(self.parse_assignment_expression())); } if !self.match_token(TokenType::Comma) { break; } self.consume(); } self.consume_token(TokenType::BracketClose); self.expression(start, ExpressionKind::Array(Box::new(elements))) } /// Parse a template literal (`` `...${expression}...` ``). // https://tc39.es/ecma262/#sec-template-literals // TemplateLiteral : NoSubstitutionTemplate // | SubstitutionTemplate // SubstitutionTemplate : TemplateHead Expression TemplateSpans // NB: In tagged templates, invalid escape sequences produce `undefined` for the // cooked value instead of a syntax error (sec-template-literals-static-semantics-early-errors). /// Consume any tagged template literals following an expression. /// Tagged templates bind tighter than any binary operator, so they /// are handled outside the normal precedence loop. fn parse_tagged_template_literals(&mut self, tag_start: Position, mut expression: Expression) -> Expression { while self.match_token(TokenType::TemplateLiteralStart) { let template = self.parse_template_literal(true); expression = self.expression( tag_start, ExpressionKind::TaggedTemplateLiteral(Box::new(TaggedTemplateData { tag: Box::new(expression), template_literal: Box::new(template), })), ); } expression } pub(crate) fn parse_template_literal(&mut self, is_tagged: bool) -> Expression { let start = self.position(); self.consume_token(TokenType::TemplateLiteralStart); let mut expressions = Vec::new(); let mut raw_strings = Vec::new(); let needs_leading_empty = !self.match_token(TokenType::TemplateLiteralString); if needs_leading_empty { if is_tagged { raw_strings.push(Utf16String::new()); } expressions.push(self.expression(start, ExpressionKind::StringLiteral(Box::new(Utf16String::new())))); } // For non-tagged templates, we collect parts as expressions (alternating // string parts and interpolation expressions). For tagged templates, we // also collect raw strings separately. loop { if self.match_token(TokenType::TemplateLiteralEnd) { self.consume(); break; } if self.match_token(TokenType::TemplateLiteralString) { let token = self.consume(); // C++ calls parse_string_literal after consume(), so its position // is after the template string token. Match that behavior. let string_pos = self.position(); let raw = self.token_value(&token).to_vec(); if is_tagged { let raw_value = raw_template_value(&raw); raw_strings.push(raw_value); match self.process_template_escape_sequences(&raw) { Some(cooked) => expressions .push(self.expression(string_pos, ExpressionKind::StringLiteral(Box::new(cooked)))), // C++ uses rule_start (template literal start) for NullLiteral. None => { expressions.push(self.expression(start, ExpressionKind::NullLiteral)); } } } else { let (value, has_octal) = self.process_escape_sequences(&raw); if has_octal { self.syntax_error("Octal escape sequence not allowed in template literal"); } expressions.push(self.expression(string_pos, ExpressionKind::StringLiteral(Box::new(value)))); } } else if self.match_token(TokenType::TemplateLiteralExprStart) { self.consume(); let expression = self.parse_expression_any(); expressions.push(expression); self.consume_token(TokenType::TemplateLiteralExprEnd); // After an expression, if no template string follows, insert empty. if !self.match_token(TokenType::TemplateLiteralString) { expressions .push(self.expression(start, ExpressionKind::StringLiteral(Box::new(Utf16String::new())))); if is_tagged { raw_strings.push(Utf16String::new()); } } } else if self.done() { self.expected("template literal end"); break; } else { self.consume(); } } self.expression( start, ExpressionKind::TemplateLiteral(Box::new(TemplateLiteralData { expressions, raw_strings, })), ) } fn process_template_escape_sequences(&self, raw: &[u16]) -> Option { let result = process_escape_sequences_impl(raw, EscapeMode::TaggedTemplate); if result.failed { None } else { Some(result.value) } } /// Parse a string literal token's value, processing escape sequences. /// Returns `(value, has_legacy_octal)`. pub(crate) fn parse_string_value(&mut self, token: &Token) -> (Utf16String, bool) { let raw = self.token_value(token).to_vec(); if raw.len() < 2 { return (Utf16String::default(), false); } self.process_escape_sequences(&raw[1..raw.len() - 1]) } pub(crate) fn process_escape_sequences(&mut self, inner: &[u16]) -> (Utf16String, bool) { let result = process_escape_sequences_impl(inner, EscapeMode::StringLiteral); if result.malformed_hex { self.syntax_error("Malformed hexadecimal escape sequence"); } if result.malformed_unicode { self.syntax_error("Malformed unicode escape sequence"); } (result.value, result.has_legacy_octal) } } struct EscapeResult { value: Utf16String, has_legacy_octal: bool, /// Tagged template encountered an invalid escape. failed: bool, malformed_hex: bool, malformed_unicode: bool, } /// Unified escape sequence processor for both string and template literals. /// /// In `TaggedTemplate` mode, `failed` is true when an invalid escape is /// encountered (the cooked value becomes `undefined`). fn process_escape_sequences_impl(input: &[u16], mode: EscapeMode) -> EscapeResult { let mut result = Utf16String(Vec::with_capacity(input.len())); let mut has_legacy_octal = false; let mut malformed_hex = false; let mut malformed_unicode = false; let mut i = 0; const N: u16 = ch(b'n'); const R: u16 = ch(b'r'); const T: u16 = ch(b't'); const B: u16 = ch(b'b'); const F: u16 = ch(b'f'); const V: u16 = ch(b'v'); const ZERO: u16 = ch(b'0'); const ONE: u16 = ch(b'1'); const SEVEN: u16 = ch(b'7'); const EIGHT: u16 = ch(b'8'); const NINE: u16 = ch(b'9'); const X: u16 = ch(b'x'); const U: u16 = ch(b'u'); const LF: u16 = ch(b'\n'); const CR: u16 = ch(b'\r'); const LS: u16 = 0x2028; const PS: u16 = 0x2029; while i < input.len() { if input[i] == b'\\' as u16 && i + 1 < input.len() { i += 1; match input[i] { N => result.0.push(ch(b'\n')), R => result.0.push(ch(b'\r')), T => result.0.push(ch(b'\t')), B => result.0.push(8), F => result.0.push(12), V => result.0.push(11), ZERO => { if mode == EscapeMode::TaggedTemplate { if i + 1 < input.len() && (is_octal_char(input[i + 1]) || input[i + 1] == EIGHT || input[i + 1] == NINE) { return EscapeResult { value: result, has_legacy_octal: false, failed: true, malformed_hex: false, malformed_unicode: false, }; } result.0.push(0); } else if i + 1 < input.len() && is_octal_char(input[i + 1]) { has_legacy_octal = true; let (val, consumed) = parse_octal_escape(input, i); result.0.push(val); i += consumed; } else if i + 1 < input.len() && (input[i + 1] == EIGHT || input[i + 1] == NINE) { has_legacy_octal = true; result.0.push(0); } else { result.0.push(0); } } ONE..=SEVEN => { if mode == EscapeMode::TaggedTemplate { return EscapeResult { value: result, has_legacy_octal: false, failed: true, malformed_hex: false, malformed_unicode: false, }; } has_legacy_octal = true; let (val, consumed) = parse_octal_escape(input, i); result.0.push(val); i += consumed; } EIGHT | NINE => { if mode == EscapeMode::TaggedTemplate { return EscapeResult { value: result, has_legacy_octal: false, failed: true, malformed_hex: false, malformed_unicode: false, }; } has_legacy_octal = true; result.0.push(input[i]); } X => { if let Some((advance, ch)) = parse_hex_escape(input, i) { result.0.push(ch); i += advance; } else if mode == EscapeMode::TaggedTemplate { return EscapeResult { value: result, has_legacy_octal: false, failed: true, malformed_hex: false, malformed_unicode: false, }; } else { malformed_hex = true; result.0.push(input[i]); } } U => { if let Some((advance, code_point)) = parse_unicode_escape(input, i) { push_code_point(&mut result.0, code_point); i += advance; } else if mode == EscapeMode::TaggedTemplate { return EscapeResult { value: result, has_legacy_octal: false, failed: true, malformed_hex: false, malformed_unicode: false, }; } else { malformed_unicode = true; result.0.push(input[i]); } } LF => { /* line continuation */ } CR => { if i + 1 < input.len() && input[i + 1] == LF { i += 1; } } LS | PS => { /* skip LS/PS */ } c => result.0.push(c), } } else if input[i] == ch(b'\r') { // Normalize \r\n and bare \r to \n per spec (12.9.6). result.0.push(ch(b'\n')); if i + 1 < input.len() && input[i + 1] == ch(b'\n') { i += 1; } } else { result.0.push(input[i]); } i += 1; } EscapeResult { value: result, has_legacy_octal, failed: false, malformed_hex, malformed_unicode, } } impl Parser<'_> { /// Try to parse an arrow function expression, with memoization. /// If a previous attempt at this position already failed, returns `None` /// immediately. Otherwise attempts the parse and caches the failure. /// This prevents exponential re-processing of nested expressions like /// `(a=(b=(c=0)))` where each outer failed arrow attempt would re-trigger /// inner attempts during grouping expression re-parse. // https://tc39.es/ecma262/#sec-arrow-function-definitions // ArrowFunction : ArrowParameters [no LineTerminator here] `=>` ConciseBody // ConciseBody : [lookahead != `{`] ExpressionBody // | `{` FunctionBody `}` pub(crate) fn try_parse_arrow_function_expression( &mut self, expect_parens: bool, is_async: bool, source_start_override: Option, forbidden: ForbiddenTokens, ) -> Option { let offset = source_start_override.map_or(self.current_token.offset, |p| p.offset) as usize; if self.arrow_function_failed_positions.contains(&offset) { return None; } let result = self.try_parse_arrow_function_expression_impl(expect_parens, is_async, source_start_override, forbidden); if result.is_none() { self.arrow_function_failed_positions.insert(offset); } result } fn try_parse_arrow_function_expression_impl( &mut self, expect_parens: bool, is_async: bool, source_start_override: Option, forbidden: ForbiddenTokens, ) -> Option { let start = source_start_override.unwrap_or_else(|| self.position()); if !expect_parens && !is_async { if !self.match_identifier() && !self.match_token(TokenType::Await) && !self.match_token(TokenType::Yield) { return None; } let next = self.next_token(); if next.token_type != TokenType::Arrow || next.trivia_has_line_terminator { return None; } } // Save and clear pattern_bound_names so that parameter and body // parsing inside this arrow function doesn't steal binding names // accumulated by an outer binding pattern context. let saved_pattern_bound_names = std::mem::take(&mut self.pattern_bound_names); self.save_state(); // Reset in_formal_parameter_context so that yield/await inside // arrow function bodies nested in parameter defaults are not // rejected. (load_state restores flags on error paths.) let saved_formal_parameter_ctx = self.flags.in_formal_parameter_context; self.flags.in_formal_parameter_context = false; if is_async { self.consume(); // consume 'async' if self.current_token.trivia_has_line_terminator { self.pattern_bound_names = saved_pattern_bound_names; self.load_state(); return None; } if expect_parens { self.consume_token(TokenType::ParenOpen); } } // Open function scope before parsing parameters so that default // value expressions are resolved inside the function scope. // save_state() above captured scope collector state, so any // load_state() rollback will undo this. self.scope_collector.open_function_scope(None); self.scope_collector.set_is_arrow_function(); self.push_function_context(); // Set await_expression_is_valid during parameter parsing so that // 'await' is rejected as an identifier in async arrow parameters. let saved_await = self.flags.await_expression_is_valid; let saved_static_init = self.flags.in_class_static_init_block; if is_async { self.flags.await_expression_is_valid = true; } // NB: Do NOT clear in_class_static_init_block here. Arrow functions don't // create a new `await` boundary, so `await` in parameter defaults must still // be rejected inside class static initializer blocks. The flag is cleared // below, after parameter parsing, for the arrow body only. let parsed; if expect_parens { let previous_errors = self.errors.len(); parsed = self.parse_formal_parameters_impl(true); if self.errors.len() > previous_errors { self.pattern_bound_names = saved_pattern_bound_names; self.load_state(); return None; } if !self.match_token(TokenType::ParenClose) { self.pattern_bound_names = saved_pattern_bound_names; self.load_state(); return None; } self.consume(); // consume ')' } else if self.match_identifier() || self.match_token(TokenType::Await) { let token = self.consume(); let value = self.token_value(&token).to_vec(); if value == utf16!("await") && (is_async || self.program_type == ProgramType::Module || self.flags.in_class_static_init_block) { self.syntax_error("'await' is not allowed as an identifier in this context"); } // C++ uses rule_start (arrow function start, which is `async` for async arrows). let value_id = self.arena.strings.intern(&value); let binding = self .arena .identifiers .insert(Identifier::new(self.range_from(start), value_id)); parsed = ParsedParameters { parameters: vec![FunctionParameter { binding: FunctionParameterBinding::Identifier(binding), default_value: None, is_rest: false, }], function_length: 1, parameter_info: vec![ParamInfo { name: value.into(), is_rest: false, is_from_pattern: false, identifier: Some(binding), }], is_simple: true, }; } else { self.flags.await_expression_is_valid = saved_await; self.pattern_bound_names = saved_pattern_bound_names; self.load_state(); return None; } // Restore await flag during arrow-check; it will be set // again for the body below. self.flags.await_expression_is_valid = saved_await; // [no LineTerminator here] before `=>` if !self.match_token(TokenType::Arrow) || self.current_token.trivia_has_line_terminator { self.pattern_bound_names = saved_pattern_bound_names; self.load_state(); return None; } self.consume(); // consume => self.discard_saved_state(); let ParsedParameters { parameters, function_length, parameter_info, is_simple, } = parsed; // Arrow functions always reject duplicate parameter names. self.check_arrow_duplicate_parameters(¶meter_info); self.register_function_parameters_with_scope(¶meters, ¶meter_info); let fn_kind = if is_async { FunctionKind::Async } else { FunctionKind::Normal }; let src_start = source_start_override.unwrap_or(start).offset; // Set context flags for the arrow body. let saved_await_body = self.flags.await_expression_is_valid; self.flags.await_expression_is_valid = is_async; self.flags.in_class_static_init_block = false; if self.match_token(TokenType::CurlyOpen) { let (body, has_use_strict, insights) = self.parse_function_body(is_async, false, is_simple); self.scope_collector.close_scope(); self.pattern_bound_names = saved_pattern_bound_names; if has_use_strict || fn_kind != FunctionKind::Normal || self.flags.strict_mode { self.check_parameters_post_body(¶meter_info, has_use_strict || self.flags.strict_mode, fn_kind); } self.flags.await_expression_is_valid = saved_await_body; self.flags.in_class_static_init_block = saved_static_init; self.flags.in_formal_parameter_context = saved_formal_parameter_ctx; let nested_function_ids = self.pop_function_context(); let function_id = self.insert_function_data(FunctionData { name: None, source_text_start: src_start, source_text_end: self.source_text_end_offset(), body: Box::new(body), parameters, function_length, kind: fn_kind, is_strict_mode: self.flags.strict_mode || has_use_strict, is_arrow_function: true, parsing_insights: insights, nested_function_ids: Some(nested_function_ids), }); Some(self.expression(start, ExpressionKind::Function(function_id))) } else { let body_forbidden = if saved_formal_parameter_ctx { ForbiddenTokens::none() } else { forbidden }; // https://tc39.es/ecma262/#prod-ArrowFunction // ArrowFunction[In, Yield, Await] : // ArrowParameters[?Yield, ?Await] [no LineTerminator here] `=>` ConciseBody[?In] let expression = self.parse_expression(PRECEDENCE_ASSIGNMENT, Associativity::Right, body_forbidden); // C++ uses rule_start (function start) for ReturnStatement and FunctionBody. let return_statement = Statement::new( self.range_from(start), StatementKind::Return(Some(Box::new(expression))), ); let scope = self.make_scope(vec![return_statement]); self.scope_collector.set_scope_node(scope); let body = Statement::new( self.range_from(start), StatementKind::FunctionBody { scope, in_strict_mode: self.flags.strict_mode, }, ); // C++ only sets contains_direct_call_to_eval and uses_this_from_environment // for expression-body arrows (not uses_this). let insights = FunctionParsingInsights { contains_direct_call_to_eval: self.scope_collector.contains_direct_call_to_eval(), uses_this_from_environment: self.scope_collector.uses_this_from_environment(), ..FunctionParsingInsights::default() }; self.scope_collector.close_scope(); self.pattern_bound_names = saved_pattern_bound_names; if self.flags.strict_mode || fn_kind != FunctionKind::Normal { self.check_parameters_post_body(¶meter_info, self.flags.strict_mode, fn_kind); } self.flags.await_expression_is_valid = saved_await_body; self.flags.in_class_static_init_block = saved_static_init; self.flags.in_formal_parameter_context = saved_formal_parameter_ctx; let nested_function_ids = self.pop_function_context(); let function_id = self.insert_function_data(FunctionData { name: None, source_text_start: src_start, source_text_end: self.source_text_end_offset(), body: Box::new(body), parameters, function_length, kind: fn_kind, is_strict_mode: self.flags.strict_mode, is_arrow_function: true, parsing_insights: insights, nested_function_ids: Some(nested_function_ids), }); Some(self.expression(start, ExpressionKind::Function(function_id))) } } pub(crate) fn parse_method_definition( &mut self, is_async: bool, is_generator: bool, method_kind: MethodKind, function_start: Position, ) -> Expression { let start = function_start; let saved_might_need_arguments = self.flags.function_might_need_arguments_object; self.flags.function_might_need_arguments_object = false; let fn_kind = FunctionKind::from_async_generator(is_async, is_generator); // Open function scope for method. self.scope_collector.open_function_scope(None); let in_generator_before = self.flags.in_generator_function_context; let await_before = self.flags.await_expression_is_valid; let saved_static_init = self.flags.in_class_static_init_block; let saved_field_init = self.flags.in_class_field_initializer; let saved_allow_super_call = self.flags.allow_super_constructor_call; let saved_allow_super_lookup = self.flags.allow_super_property_lookup; let saved_new_target = self.flags.new_target_is_valid; self.flags.in_generator_function_context = is_generator; self.flags.await_expression_is_valid = is_async; self.flags.in_class_static_init_block = false; self.flags.in_class_field_initializer = false; self.flags.allow_super_constructor_call = method_kind == MethodKind::Constructor && self.class_has_super_class; self.flags.allow_super_property_lookup = true; self.flags.new_target_is_valid = true; // Save pattern_bound_names so that destructuring patterns in the // method body don't steal names from an outer binding context. let saved_pattern_bound_names = std::mem::take(&mut self.pattern_bound_names); self.push_function_context(); let parsed = self.parse_formal_parameters(); // UniqueFormalParameters : FormalParameters // It is a Syntax Error if the BoundNames of |FormalParameters| contains any duplicate elements. self.check_unique_formal_parameters(&parsed.parameter_info); self.register_function_parameters_with_scope(&parsed.parameters, &parsed.parameter_info); if method_kind == MethodKind::Getter && !parsed.parameters.is_empty() { self.syntax_error("Getter function must have no arguments"); } if method_kind == MethodKind::Setter && (parsed.parameters.len() != 1 || parsed.parameters.first().is_some_and(|p| p.is_rest)) { self.syntax_error("Setter function must have one argument"); } self.flags.in_generator_function_context = in_generator_before; self.flags.await_expression_is_valid = await_before; let (body, has_use_strict, mut insights) = self.parse_function_body(is_async, is_generator, parsed.is_simple); self.flags.allow_super_constructor_call = saved_allow_super_call; self.flags.allow_super_property_lookup = saved_allow_super_lookup; self.scope_collector.close_scope(); self.pattern_bound_names = saved_pattern_bound_names; // Check parameters before restoring flags so that the method's // context is used (e.g. in_class_static_init_block must remain // false to allow `await` as a parameter name in generators). let parameters_are_strict = self.flags.strict_mode || has_use_strict; if parameters_are_strict || fn_kind != FunctionKind::Normal { self.check_parameters_post_body(&parsed.parameter_info, parameters_are_strict, fn_kind); } self.flags.in_class_static_init_block = saved_static_init; self.flags.in_class_field_initializer = saved_field_init; self.flags.new_target_is_valid = saved_new_target; insights.might_need_arguments_object = self.flags.function_might_need_arguments_object; self.flags.function_might_need_arguments_object = saved_might_need_arguments; // Class constructors always need a function environment for `this` binding // management (super() binds this in derived constructors, and base constructors // need it for OrdinaryCallBindThis). if method_kind == MethodKind::Constructor { insights.uses_this = true; insights.uses_this_from_environment = true; } let nested_function_ids = self.pop_function_context(); let function_id = self.insert_function_data(FunctionData { name: None, source_text_start: function_start.offset, source_text_end: self.source_text_end_offset(), body: Box::new(body), parameters: parsed.parameters, function_length: parsed.function_length, kind: fn_kind, is_strict_mode: self.flags.strict_mode || has_use_strict, is_arrow_function: false, parsing_insights: insights, nested_function_ids: Some(nested_function_ids), }); self.expression(start, ExpressionKind::Function(function_id)) } } fn hex_digit(c: u16) -> Option { match c { 0x30..=0x39 => Some(c - 0x30), 0x41..=0x46 => Some(c - 0x41 + 10), 0x61..=0x66 => Some(c - 0x61 + 10), _ => None, } } fn is_octal_char(c: u16) -> bool { c >= ch(b'0') && c <= ch(b'7') } fn parse_octal_escape(inner: &[u16], i: usize) -> (u16, usize) { let first = (inner[i] - ch(b'0')) as u32; let mut value = first; let mut consumed = 0; if i + 1 < inner.len() && is_octal_char(inner[i + 1]) { value = value * 8 + (inner[i + 1] - ch(b'0')) as u32; consumed = 1; if i + 2 < inner.len() && is_octal_char(inner[i + 2]) && first <= 3 { value = value * 8 + (inner[i + 2] - ch(b'0')) as u32; consumed = 2; } } (value as u16, consumed) } fn parse_hex_escape(raw: &[u16], i: usize) -> Option<(usize, u16)> { if i + 2 >= raw.len() { return None; } let high = hex_digit(raw[i + 1])?; let low = hex_digit(raw[i + 2])?; Some((2, high * 16 + low)) } fn parse_unicode_escape(raw: &[u16], i: usize) -> Option<(usize, u32)> { if i + 1 >= raw.len() { return None; } if raw[i + 1] == ch(b'{') { let mut j = i + 2; let mut value: u32 = 0; let mut digits = 0; while j < raw.len() && raw[j] != ch(b'}') { let d = hex_digit(raw[j])? as u32; value = value * 16 + d; if value > 0x10FFFF { return None; } digits += 1; j += 1; } if j >= raw.len() || digits == 0 { return None; } Some((j - i, value)) } else { if i + 4 >= raw.len() { return None; } let d0 = hex_digit(raw[i + 1])? as u32; let d1 = hex_digit(raw[i + 2])? as u32; let d2 = hex_digit(raw[i + 3])? as u32; let d3 = hex_digit(raw[i + 4])? as u32; Some((4, (d0 << 12) | (d1 << 8) | (d2 << 4) | d3)) } } fn push_code_point(result: &mut Vec, code_point: u32) { if code_point > 0xFFFF { let adjusted = code_point - 0x10000; result.push((0xD800 | ((adjusted >> 10) & 0x3FF)) as u16); result.push((0xDC00 | (adjusted & 0x3FF)) as u16); } else { result.push(code_point as u16); } } fn raw_template_value(raw: &[u16]) -> Utf16String { let mut result = Utf16String(Vec::with_capacity(raw.len())); let mut i = 0; while i < raw.len() { if raw[i] == ch(b'\r') { result.0.push(ch(b'\n')); if i + 1 < raw.len() && raw[i + 1] == ch(b'\n') { i += 1; } } else { result.0.push(raw[i]); } i += 1; } result } fn token_to_binary_op(tt: TokenType) -> BinaryOp { match tt { TokenType::Plus => BinaryOp::Addition, TokenType::Minus => BinaryOp::Subtraction, TokenType::Asterisk => BinaryOp::Multiplication, TokenType::Slash => BinaryOp::Division, TokenType::Percent => BinaryOp::Modulo, TokenType::DoubleAsterisk => BinaryOp::Exponentiation, TokenType::EqualsEqualsEquals => BinaryOp::StrictlyEquals, TokenType::ExclamationMarkEqualsEquals => BinaryOp::StrictlyInequals, TokenType::EqualsEquals => BinaryOp::LooselyEquals, TokenType::ExclamationMarkEquals => BinaryOp::LooselyInequals, TokenType::GreaterThan => BinaryOp::GreaterThan, TokenType::GreaterThanEquals => BinaryOp::GreaterThanEquals, TokenType::LessThan => BinaryOp::LessThan, TokenType::LessThanEquals => BinaryOp::LessThanEquals, TokenType::Ampersand => BinaryOp::BitwiseAnd, TokenType::Pipe => BinaryOp::BitwiseOr, TokenType::Caret => BinaryOp::BitwiseXor, TokenType::ShiftLeft => BinaryOp::LeftShift, TokenType::ShiftRight => BinaryOp::RightShift, TokenType::UnsignedShiftRight => BinaryOp::UnsignedRightShift, TokenType::In => BinaryOp::In, TokenType::Instanceof => BinaryOp::InstanceOf, _ => unreachable!("unexpected token {:?} in binary expression", tt), } } fn token_to_assignment_op(tt: TokenType) -> AssignmentOp { match tt { TokenType::Equals => AssignmentOp::Assignment, TokenType::PlusEquals => AssignmentOp::AdditionAssignment, TokenType::MinusEquals => AssignmentOp::SubtractionAssignment, TokenType::AsteriskEquals => AssignmentOp::MultiplicationAssignment, TokenType::SlashEquals => AssignmentOp::DivisionAssignment, TokenType::PercentEquals => AssignmentOp::ModuloAssignment, TokenType::DoubleAsteriskEquals => AssignmentOp::ExponentiationAssignment, TokenType::AmpersandEquals => AssignmentOp::BitwiseAndAssignment, TokenType::PipeEquals => AssignmentOp::BitwiseOrAssignment, TokenType::CaretEquals => AssignmentOp::BitwiseXorAssignment, TokenType::ShiftLeftEquals => AssignmentOp::LeftShiftAssignment, TokenType::ShiftRightEquals => AssignmentOp::RightShiftAssignment, TokenType::UnsignedShiftRightEquals => AssignmentOp::UnsignedRightShiftAssignment, TokenType::DoubleAmpersandEquals => AssignmentOp::AndAssignment, TokenType::DoublePipeEquals => AssignmentOp::OrAssignment, TokenType::DoubleQuestionMarkEquals => AssignmentOp::NullishAssignment, _ => unreachable!("unexpected token {:?} in assignment expression", tt), } } pub(crate) fn parse_numeric_value(value: &[u16]) -> f64 { let s: String = value .iter() .filter(|&&c| c != '_' as u16) .map(|&c| c as u8 as char) .collect(); if s.starts_with("0x") || s.starts_with("0X") { parse_integer_with_radix(&s[2..], 16) } else if s.starts_with("0o") || s.starts_with("0O") { parse_integer_with_radix(&s[2..], 8) } else if s.starts_with("0b") || s.starts_with("0B") { parse_integer_with_radix(&s[2..], 2) } else if s.starts_with('0') && s.len() > 1 && s.as_bytes()[1].is_ascii_digit() { let digits = &s[1..]; if digits.bytes().all(|b| (b'0'..=b'7').contains(&b)) { parse_integer_with_radix(digits, 8) } else { s.parse::().unwrap_or(f64::NAN) } } else { s.parse::().unwrap_or(f64::NAN) } } fn parse_integer_with_radix(digits: &str, radix: u32) -> f64 { if let Ok(v) = u64::from_str_radix(digits, radix) { return v as f64; } let mut result: f64 = 0.0; for ch in digits.chars() { let digit = ch.to_digit(radix); if let Some(d) = digit { result = result * (radix as f64) + (d as f64); } } result }