ladybird/Libraries/LibJS/Rust/src/parser/statements.rs
2026-03-28 11:55:41 +01:00

1075 lines
42 KiB
Rust

/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
//! Statement parsing: if, for, while, switch, try, etc.
use std::collections::HashSet;
use std::rc::Rc;
use crate::ast::*;
use crate::parser::{Associativity, ForbiddenTokens, PRECEDENCE_COMMA, Parser, Position};
use crate::token::TokenType;
/// Used locally during for-statement parsing before converting to `ast::ForInit`.
enum LocalForInit {
Declaration(Statement),
Expression(Expression),
}
impl Parser<'_> {
pub(crate) fn parse_statement(&mut self, allow_labelled_function: bool) -> Statement {
let start = self.position();
let tt = self.current_token_type();
match tt {
TokenType::CurlyOpen => self.parse_block_statement(),
TokenType::Return => self.parse_return_statement(),
TokenType::Var => self.parse_variable_declaration(false),
TokenType::For => self.parse_for_statement(),
TokenType::If => self.parse_if_statement(),
TokenType::Throw => self.parse_throw_statement(),
TokenType::Try => self.parse_try_statement(),
TokenType::Break => self.parse_break_statement(),
TokenType::Continue => self.parse_continue_statement(),
TokenType::Switch => self.parse_switch_statement(),
TokenType::Do => self.parse_do_while_statement(),
TokenType::While => self.parse_while_statement(),
TokenType::With => {
if self.flags.strict_mode {
self.syntax_error("'with' statement not allowed in strict mode");
}
self.parse_with_statement()
}
TokenType::Debugger => self.parse_debugger_statement(),
TokenType::Semicolon => {
self.consume();
self.statement(start, StatementKind::Empty)
}
TokenType::Slash | TokenType::SlashEquals => {
let token = self.lexer.force_slash_as_regex();
self.current_token = token;
self.parse_expression_statement()
}
_ => {
if self.match_invalid_escaped_keyword() {
self.syntax_error("Keyword must not contain escaped characters");
}
if self.match_identifier_name()
&& let Some(labelled) =
self.try_parse_labelled_statement(allow_labelled_function)
{
return labelled;
}
if self.match_expression() {
self.parse_expression_statement()
} else {
self.expected("statement");
self.consume();
self.statement(start, StatementKind::Empty)
}
}
}
}
pub(crate) fn parse_block_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::CurlyOpen);
self.scope_collector.open_block_scope(None);
let mut children = Vec::new();
while !self.match_token(TokenType::CurlyClose) && !self.done() {
if self.match_declaration() {
children.push(self.parse_declaration());
} else {
children.push(self.parse_statement(true));
}
}
self.consume_token(TokenType::CurlyClose);
let scope = ScopeData::shared_with_children(children);
self.scope_collector.set_scope_node(scope.clone());
self.scope_collector.close_scope();
self.statement(start, StatementKind::Block(scope))
}
fn parse_expression_statement(&mut self) -> Statement {
let start = self.position();
if self.match_token(TokenType::Async) {
let lookahead = self.next_token();
if lookahead.token_type == TokenType::Function && !lookahead.trivia_has_line_terminator
{
self.syntax_error(
"Async function declaration not allowed in single-statement context",
);
}
} else if self.match_token(TokenType::Function) || self.match_token(TokenType::Class) {
let name = self.current_token.token_type.name();
self.syntax_error(&format!(
"{name} declaration not allowed in single-statement context"
));
} else if self.match_token(TokenType::Let)
&& self.next_token().token_type == TokenType::BracketOpen
{
self.syntax_error("let followed by [ is not allowed in single-statement context");
}
let expression = self.parse_expression_any();
self.consume_or_insert_semicolon();
self.statement(start, StatementKind::Expression(Box::new(expression)))
}
// https://tc39.es/ecma262/#sec-return-statement
// ReturnStatement : `return` [no LineTerminator here] Expression? `;`
fn parse_return_statement(&mut self) -> Statement {
let start = self.position();
if !self.flags.in_function_context {
self.syntax_error("'return' not allowed outside of a function");
}
self.consume_token(TokenType::Return);
// [no LineTerminator here]: if a line terminator follows `return`,
// ASI inserts a semicolon and the return has no argument.
// NB: Don't consume the next token — it may be `;` which should
// become an EmptyStatement, not be swallowed by the return.
if self.current_token.trivia_has_line_terminator {
return self.statement(start, StatementKind::Return(None));
}
if self.match_token(TokenType::Semicolon)
|| self.match_token(TokenType::CurlyClose)
|| self.done()
{
self.consume_or_insert_semicolon();
return self.statement(start, StatementKind::Return(None));
}
let argument = self.parse_expression_any();
self.consume_or_insert_semicolon();
self.statement(start, StatementKind::Return(Some(Box::new(argument))))
}
// https://tc39.es/ecma262/#sec-throw-statement
// ThrowStatement : `throw` [no LineTerminator here] Expression `;`
fn parse_throw_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::Throw);
// [no LineTerminator here]: unlike `return`, a line terminator after
// `throw` is always an error because `throw;` is never valid.
if self.current_token.trivia_has_line_terminator {
self.syntax_error("No line break is allowed between 'throw' and its expression");
}
let argument = self.parse_expression_any();
self.consume_or_insert_semicolon();
self.statement(start, StatementKind::Throw(Box::new(argument)))
}
// https://tc39.es/ecma262/#sec-break-statement
// BreakStatement : `break` [no LineTerminator here] LabelIdentifier? `;`
fn parse_break_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::Break);
let label = if self.match_token(TokenType::Semicolon) {
self.consume();
None
} else if !self.current_token.trivia_has_line_terminator
&& !self.match_token(TokenType::CurlyClose)
&& !self.done()
&& self.match_identifier()
{
let token = self.consume();
let label_value = Utf16String::from(self.token_value(&token));
if !self.labels_in_scope.contains_key(label_value.as_slice()) {
let label_str = String::from_utf16_lossy(&label_value);
self.syntax_error(&format!("Label '{label_str}' not found"));
}
self.consume_or_insert_semicolon();
Some(label_value)
} else {
self.consume_or_insert_semicolon();
None
};
if label.is_none() && !self.flags.in_break_context {
self.syntax_error(
"Unlabeled 'break' not allowed outside of a loop or switch statement",
);
}
self.statement(
start,
StatementKind::Break {
target_label: label,
},
)
}
// https://tc39.es/ecma262/#sec-continue-statement
// ContinueStatement : `continue` [no LineTerminator here] LabelIdentifier? `;`
fn parse_continue_statement(&mut self) -> Statement {
let start = self.position();
if !self.flags.in_continue_context {
self.syntax_error("'continue' not allowed outside of a loop");
}
self.consume_token(TokenType::Continue);
let label = if self.match_token(TokenType::Semicolon) {
None
} else if !self.current_token.trivia_has_line_terminator
&& !self.match_token(TokenType::CurlyClose)
&& !self.done()
&& self.match_identifier()
{
let label_line = self.current_token.line_number;
let label_col = self.current_token.line_column;
let token = self.consume();
let label_value = Utf16String::from(self.token_value(&token));
if let Some(entry) = self.labels_in_scope.get_mut(label_value.as_slice()) {
*entry = Some((label_line, label_col));
} else {
let label_str = String::from_utf16_lossy(&label_value);
self.syntax_error(&format!("Label '{label_str}' not found or invalid"));
}
Some(label_value)
} else {
None
};
self.consume_or_insert_semicolon();
self.statement(
start,
StatementKind::Continue {
target_label: label,
},
)
}
fn parse_debugger_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::Debugger);
self.consume_or_insert_semicolon();
self.statement(start, StatementKind::Debugger)
}
fn parse_if_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::If);
self.consume_token(TokenType::ParenOpen);
let predicate = self.parse_expression_any();
self.consume_token(TokenType::ParenClose);
// https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses
// Annex B allows FunctionDeclarations (but NOT GeneratorDeclarations) in
// IfStatement bodies in sloppy mode.
let consequent = if !self.flags.strict_mode
&& self.match_token(TokenType::Function)
&& self.next_token().token_type != TokenType::Asterisk
{
self.parse_function_declaration_as_block_statement(start)
} else {
self.parse_statement(false)
};
let alternate = if self.match_token(TokenType::Else) {
self.consume();
if !self.flags.strict_mode
&& self.match_token(TokenType::Function)
&& self.next_token().token_type != TokenType::Asterisk
{
Some(Box::new(
self.parse_function_declaration_as_block_statement(start),
))
} else {
Some(Box::new(self.parse_statement(false)))
}
} else {
None
};
self.statement(
start,
StatementKind::If {
test: Box::new(predicate),
consequent: Box::new(consequent),
alternate,
},
)
}
/// Annex B: Parse a function declaration as if wrapped in a synthetic block.
/// See https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses
fn parse_function_declaration_as_block_statement(&mut self, if_start: Position) -> Statement {
// C++ uses rule_start from the enclosing if-statement for the block position.
let start = if_start;
self.scope_collector.open_block_scope(None);
let declaration = self.parse_function_declaration();
let scope = ScopeData::shared_with_children(vec![declaration]);
self.scope_collector.set_scope_node(scope.clone());
self.scope_collector.close_scope();
self.statement(start, StatementKind::Block(scope))
}
/// Parse a statement in a loop body context (break and continue allowed).
fn parse_loop_body(&mut self) -> Statement {
let break_before = self.flags.in_break_context;
let continue_before = self.flags.in_continue_context;
self.flags.in_break_context = true;
self.flags.in_continue_context = true;
let body = self.parse_statement(false);
self.flags.in_break_context = break_before;
self.flags.in_continue_context = continue_before;
body
}
fn parse_while_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::While);
self.consume_token(TokenType::ParenOpen);
let test = self.parse_expression_any();
self.consume_token(TokenType::ParenClose);
let body = self.parse_loop_body();
self.statement(
start,
StatementKind::While {
test: Box::new(test),
body: Box::new(body),
},
)
}
fn parse_do_while_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::Do);
let body = self.parse_loop_body();
self.consume_token(TokenType::While);
self.consume_token(TokenType::ParenOpen);
let test = self.parse_expression_any();
self.consume_token(TokenType::ParenClose);
// Since ES 2015 a missing semicolon is inserted here, despite
// the regular ASI rules not applying.
self.eat(TokenType::Semicolon);
self.statement(
start,
StatementKind::DoWhile {
test: Box::new(test),
body: Box::new(body),
},
)
}
// https://tc39.es/ecma262/#sec-for-statement
// https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
fn parse_for_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::For);
// Open for-loop scope (for let/const/using declarations).
// scope_data set after the for-loop body is parsed.
self.scope_collector.open_for_loop_scope(None);
let is_await = if self.match_token(TokenType::Await) {
if !self.flags.await_expression_is_valid {
self.syntax_error("for-await-of not allowed outside of async context");
}
self.consume();
true
} else {
false
};
self.consume_token(TokenType::ParenOpen);
if self.match_token(TokenType::Semicolon) && !is_await {
self.consume();
let result = self.parse_standard_for_loop(start, None);
return self.close_for_loop_scope(start, result);
}
let init_start = self.position();
let init_starts_with_async_keyword = self.match_token(TokenType::Async);
let is_var_init = self.match_token(TokenType::Var);
let is_using = self.match_for_using_declaration();
let is_let = self.match_token(TokenType::Let)
&& (self.flags.strict_mode || self.try_match_let_declaration());
let is_declaration =
is_var_init || is_using || is_let || self.match_token(TokenType::Const);
let init = if is_using {
LocalForInit::Declaration(self.parse_using_declaration(true))
} else if is_declaration {
LocalForInit::Declaration(self.parse_variable_declaration(true))
} else {
let forbidden = ForbiddenTokens::with_in();
LocalForInit::Expression(self.parse_expression(
PRECEDENCE_COMMA,
Associativity::Right,
forbidden,
))
};
// Check for in
// https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
// It is a Syntax Error if IsDestructuring of ForBinding is false and
// Initializer is present. (Only a single, non-initialized ForBinding
// is allowed; except in Annex B sloppy var with one declarator.)
if self.match_token(TokenType::In) && !is_await {
// C++ captures ForInStatement position at the `in` keyword.
let forin_start = self.position();
if is_using {
self.syntax_error("Using declaration not allowed in for-in loop");
} else if is_declaration {
if self.for_loop_declaration_count > 1 {
self.syntax_error("Multiple declarations not allowed in for..in/of");
}
if self.for_loop_declaration_has_init {
// https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads
// Annex B: In sloppy mode, a single `var` with a simple
// BindingIdentifier and an initializer is permitted.
// Binding patterns with initializers are never allowed.
if !self.for_loop_declaration_is_var
|| self.for_loop_declaration_count != 1
|| self.flags.strict_mode
|| self.for_loop_declaration_is_pattern
{
self.syntax_error("Variable initializer not allowed in for..in/of");
}
}
} else {
self.validate_for_in_of_lhs(&init);
}
self.consume();
let rhs = self.parse_expression_any();
self.consume_token(TokenType::ParenClose);
let body = self.parse_loop_body();
let lhs = self.synthesize_for_in_of_lhs(init, init_start);
let result = self.statement(
forin_start,
StatementKind::ForInOf {
kind: ForInOfKind::ForIn,
lhs,
rhs: Box::new(rhs),
body: Box::new(body),
},
);
return self.close_for_loop_scope(start, result);
}
// Check for of (keyword must not contain escapes)
if self.match_identifier_name() {
let value = self.token_original_value(&self.current_token);
if value == utf16!("of") {
// C++ captures ForOfStatement position at the `of` keyword.
let forof_start = self.position();
if is_declaration {
if self.for_loop_declaration_count > 1 {
self.syntax_error("Multiple declarations not allowed in for..in/of");
}
if self.for_loop_declaration_has_init {
self.syntax_error("Variable initializer not allowed in for..of");
}
} else {
self.validate_for_in_of_lhs(&init);
// https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
// `for (async of ...)` must be rejected when `async` is the
// reserved keyword token. Escaped identifiers (e.g. `\u0061sync`)
// are still valid here.
if init_starts_with_async_keyword
&& let LocalForInit::Expression(ref expression) = init
&& let ExpressionKind::Identifier(ref ident) = expression.inner
&& ident.name == utf16!("async")
{
self.syntax_error(
"for-of statement may not use 'async' as the left-hand side",
);
}
// https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
if let LocalForInit::Expression(ref expression) = init
&& let ExpressionKind::Member(ref data) = expression.inner
&& let ExpressionKind::Identifier(ref ident) = data.object.inner
&& ident.name == utf16!("let")
{
self.syntax_error("For of statement may not start with let.");
}
}
self.consume();
let rhs = self.parse_assignment_expression();
self.consume_token(TokenType::ParenClose);
let body = self.parse_loop_body();
let lhs = self.synthesize_for_in_of_lhs(init, init_start);
let for_of_kind = if is_await {
ForInOfKind::ForAwaitOf
} else {
ForInOfKind::ForOf
};
let result = self.statement(
forof_start,
StatementKind::ForInOf {
kind: for_of_kind,
lhs,
rhs: Box::new(rhs),
body: Box::new(body),
},
);
return self.close_for_loop_scope(start, result);
}
}
// Standard for loop — const requires initializer.
if let LocalForInit::Declaration(ref declaration) = init
&& let StatementKind::VariableDeclaration {
kind: DeclarationKind::Const,
ref declarations,
} = declaration.inner
{
for d in declarations {
if d.init.is_none() {
self.syntax_error("Missing initializer in const declaration");
}
}
}
self.consume_token(TokenType::Semicolon);
let for_init = match init {
LocalForInit::Declaration(declaration) => {
Some(ForInit::Declaration(Box::new(declaration)))
}
LocalForInit::Expression(expression) => Some(ForInit::Expression(Box::new(expression))),
};
let result = self.parse_standard_for_loop(start, for_init);
self.close_for_loop_scope(start, result)
}
/// Close the for-loop scope and wrap the for-loop statement in a Block
/// with scope data.
fn close_for_loop_scope(&mut self, start: Position, inner: Statement) -> Statement {
let scope = ScopeData::shared_with_children(vec![inner]);
self.scope_collector.set_scope_node(scope.clone());
self.scope_collector.close_scope();
self.statement(start, StatementKind::Block(scope))
}
fn parse_standard_for_loop(&mut self, start: Position, init: Option<ForInit>) -> Statement {
let test = if self.match_token(TokenType::Semicolon) {
None
} else {
Some(Box::new(self.parse_expression_any()))
};
self.consume_token(TokenType::Semicolon);
let update = if self.match_token(TokenType::ParenClose) {
None
} else {
Some(Box::new(self.parse_expression_any()))
};
self.consume_token(TokenType::ParenClose);
let body = self.parse_loop_body();
self.statement(
start,
StatementKind::For {
init,
test,
update,
body: Box::new(body),
},
)
}
// https://tc39.es/ecma262/#sec-with-statement
// NOTE: The with statement is forbidden in strict mode code.
fn parse_with_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::With);
self.consume_token(TokenType::ParenOpen);
let object = self.parse_expression_any();
self.consume_token(TokenType::ParenClose);
self.scope_collector.open_with_scope(None);
let body = self.parse_statement(false);
self.scope_collector.close_scope();
self.statement(
start,
StatementKind::With {
object: Box::new(object),
body: Box::new(body),
},
)
}
// https://tc39.es/ecma262/#sec-switch-statement
// All case clauses in a switch statement share a single block scope.
fn parse_switch_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::Switch);
self.consume_token(TokenType::ParenOpen);
let discriminant = self.parse_expression_any();
self.consume_token(TokenType::ParenClose);
self.consume_token(TokenType::CurlyOpen);
self.scope_collector.open_block_scope(None);
let break_before = self.flags.in_break_context;
self.flags.in_break_context = true;
let mut cases = Vec::new();
let mut has_default = false;
while !self.match_token(TokenType::CurlyClose) && !self.done() {
let case = self.parse_switch_case();
if case.test.is_none() {
if has_default {
self.syntax_error("Multiple 'default' clauses in switch statement");
}
has_default = true;
}
cases.push(case);
}
self.flags.in_break_context = break_before;
self.consume_token(TokenType::CurlyClose);
let scope = ScopeData::new_shared();
self.scope_collector.set_scope_node(scope.clone());
self.scope_collector.close_scope();
self.statement(
start,
StatementKind::Switch(Box::new(SwitchStatementData {
scope,
discriminant: Box::new(discriminant),
cases,
})),
)
}
fn parse_switch_case(&mut self) -> SwitchCase {
let start = self.position();
let test = if self.match_token(TokenType::Case) {
self.consume();
Some(self.parse_expression_any())
} else if self.match_token(TokenType::Default) {
self.consume();
None
} else {
self.expected("'case' or 'default'");
None
};
self.consume_token(TokenType::Colon);
let mut children = Vec::new();
while !self.match_token(TokenType::CurlyClose)
&& !self.match_token(TokenType::Case)
&& !self.match_token(TokenType::Default)
&& !self.done()
{
if self.match_declaration() {
children.push(self.parse_declaration());
} else {
children.push(self.parse_statement(true));
}
}
SwitchCase {
range: self.range_from(start),
scope: ScopeData::shared_with_children(children),
test,
}
}
// https://tc39.es/ecma262/#sec-try-statement
// TryStatement :
// `try` Block Catch
// `try` Block Finally
// `try` Block Catch Finally
fn parse_try_statement(&mut self) -> Statement {
let start = self.position();
self.consume_token(TokenType::Try);
let block = self.parse_block_statement();
let handler = if self.match_token(TokenType::Catch) {
Some(self.parse_catch_clause())
} else {
None
};
let finalizer = if self.match_token(TokenType::Finally) {
self.consume();
Some(Box::new(self.parse_block_statement()))
} else {
None
};
if handler.is_none() && finalizer.is_none() {
self.syntax_error("try statement must have a catch or finally clause");
}
self.statement(
start,
StatementKind::Try(Box::new(TryStatementData {
block: Box::new(block),
handler,
finalizer,
})),
)
}
// https://tc39.es/ecma262/#sec-try-statement
// Catch : `catch` `(` CatchParameter `)` Block
// | `catch` Block
// The catch parameter creates its own scope that wraps the block body.
fn parse_catch_clause(&mut self) -> CatchClause {
let start = self.position();
self.consume_token(TokenType::Catch);
self.scope_collector.open_catch_scope();
let parameter = if self.match_token(TokenType::ParenOpen) {
self.consume();
let parameter = if self.match_token(TokenType::CurlyOpen)
|| self.match_token(TokenType::BracketOpen)
{
self.pattern_bound_names.clear();
let pattern = self.parse_binding_pattern();
let names_to_check: Vec<Utf16String> = self
.pattern_bound_names
.iter()
.map(|(n, _)| n.clone())
.collect();
// https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors
// It is a Syntax Error if BoundNames of CatchParameter
// contains any duplicate elements.
{
let mut seen: HashSet<&[u16]> = HashSet::new();
for name in &names_to_check {
if !seen.insert(name.as_slice()) {
let name_str = String::from_utf16_lossy(name);
self.syntax_error(&format!(
"Duplicate binding '{name_str}' in catch parameter"
));
}
}
}
for name in &names_to_check {
self.check_identifier_name_for_assignment_validity(name, false);
}
let bound_names: Vec<&[u16]> = self
.pattern_bound_names
.iter()
.map(|(n, _)| n.as_slice())
.collect();
self.scope_collector
.add_catch_parameter_pattern(&bound_names);
// Register each binding pattern identifier for scope analysis
// so they get is_local() annotations (matching variable declarations).
for (name, id) in &self.pattern_bound_names {
self.scope_collector
.register_identifier(id.clone(), name, None);
}
Some(CatchBinding::BindingPattern(pattern))
} else if self.match_identifier() {
let parameter_start = self.position();
let token = self.consume();
let value = self.token_value(&token).to_vec();
self.check_identifier_name_for_assignment_validity(&value, false);
let id = Rc::new(Identifier::new(
self.range_from(parameter_start),
value.clone().into(),
));
self.scope_collector
.register_identifier(id.clone(), &value, None);
self.scope_collector
.add_catch_parameter_identifier(&value, id.clone());
Some(CatchBinding::Identifier(id))
} else {
self.expected("catch parameter");
None
};
self.consume_token(TokenType::ParenClose);
parameter
} else {
None
};
// Collect catch parameter names for post-body validation.
let catch_names: Vec<Utf16String> = match &parameter {
Some(CatchBinding::Identifier(id)) => vec![id.name.clone()],
Some(CatchBinding::BindingPattern(_)) => self
.pattern_bound_names
.iter()
.map(|(n, _)| n.clone())
.collect(),
None => Vec::new(),
};
let body = self.parse_block_statement();
// https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors
// It is a Syntax Error if any element of the BoundNames of
// CatchParameter also occurs in the LexicallyDeclaredNames of Block.
if !catch_names.is_empty()
&& let StatementKind::Block(ref scope) = body.inner
{
for child in &scope.borrow().children {
match &child.inner {
StatementKind::VariableDeclaration { kind, declarations }
if *kind != DeclarationKind::Var =>
{
for decl in declarations {
if let VariableDeclaratorTarget::Identifier(ref id) = decl.target {
for cn in &catch_names {
if cn.as_slice() == id.name.as_slice() {
let n = String::from_utf16_lossy(cn);
self.syntax_error(&format!(
"Identifier '{n}' already declared as catch parameter"
));
}
}
}
}
}
StatementKind::FunctionDeclaration { name: Some(id), .. } => {
for cn in &catch_names {
if cn.as_slice() == id.name.as_slice() {
let n = String::from_utf16_lossy(cn);
self.syntax_error(&format!(
"Identifier '{n}' already declared as catch parameter"
));
}
}
}
StatementKind::ClassDeclaration(data) => {
if let Some(ref id) = data.name {
for cn in &catch_names {
if cn.as_slice() == id.name.as_slice() {
let n = String::from_utf16_lossy(cn);
self.syntax_error(&format!(
"Identifier '{n}' already declared as catch parameter"
));
}
}
}
}
_ => {}
}
}
}
self.scope_collector.close_scope();
CatchClause {
range: self.range_from(start),
parameter,
body: Box::new(body),
}
}
// https://tc39.es/ecma262/#sec-labelled-statements
// It is a Syntax Error if any source text is matched by this production
// and a `continue` statement with label targets that production.
// (i.e., `continue` with a label can only target an iteration statement.)
fn try_parse_labelled_statement(&mut self, allow_labelled_function: bool) -> Option<Statement> {
let start = self.position();
if !self.match_identifier_name() {
return None;
}
self.save_state();
let token = self.consume();
let label = Utf16String::from(self.token_value(&token));
if !self.match_token(TokenType::Colon) {
self.load_state();
return None;
}
self.discard_saved_state();
self.consume(); // consume :
// https://tc39.es/ecma262/#sec-labelled-statements
// LabelIdentifier : Identifier (not ReservedWord)
// `true`, `false`, and `null` are reserved words and cannot be labels.
if token.token_type == TokenType::BoolLiteral || token.token_type == TokenType::NullLiteral
{
self.syntax_error("Reserved word cannot be used as a label");
}
if self.flags.strict_mode
&& (label == utf16!("let") || crate::parser::is_strict_reserved_word(&label))
{
self.syntax_error("Strict mode reserved word is not allowed in label");
}
if self.flags.in_generator_function_context && label == utf16!("yield") {
self.syntax_error("'yield' label is not allowed in generator function context");
}
if (self.flags.await_expression_is_valid
|| self.flags.in_class_static_init_block
|| self.program_type == crate::parser::ProgramType::Module)
&& label == utf16!("await")
{
self.syntax_error("'await' is not allowed as a label in this context");
}
if self.labels_in_scope.contains_key(label.as_slice()) {
let label_str = String::from_utf16_lossy(&label);
self.syntax_error(&format!("Label '{label_str}' has already been declared"));
}
if self.match_token(TokenType::Function)
&& (!allow_labelled_function || self.flags.strict_mode)
{
self.syntax_error("Not allowed to declare a function here");
}
if self.match_token(TokenType::Async) {
let next = self.next_token();
if next.token_type == TokenType::Function && !next.trivia_has_line_terminator {
self.syntax_error("Async functions cannot be defined in labelled statements");
}
}
self.labels_in_scope.insert(label.clone(), None);
let break_before = self.flags.in_break_context;
self.flags.in_break_context = true;
let body_starts_iteration = self.match_iteration_start();
self.last_inner_label_is_iteration = false;
let body = if self.match_token(TokenType::Function) {
let fn_decl = self.parse_function_declaration();
if let StatementKind::FunctionDeclaration { kind, .. } = fn_decl.inner {
match kind {
FunctionKind::Generator | FunctionKind::AsyncGenerator => {
self.syntax_error(
"Generator functions cannot be defined in labelled statements",
);
}
FunctionKind::Async => {
self.syntax_error(
"Async functions cannot be defined in labelled statements",
);
}
_ => {}
}
}
fn_decl
} else {
self.parse_statement(allow_labelled_function)
};
let is_iteration = body_starts_iteration || self.last_inner_label_is_iteration;
if !is_iteration && let Some(Some((line, col))) = self.labels_in_scope.get(label.as_slice())
{
self.syntax_error_at(
"labelled continue statement cannot use non iterating statement",
*line,
*col,
);
}
self.labels_in_scope.remove(label.as_slice());
self.flags.in_break_context = break_before;
self.last_inner_label_is_iteration = is_iteration;
Some(self.statement(
start,
StatementKind::Labelled {
label,
item: Box::new(body),
},
))
}
fn match_for_using_declaration(&mut self) -> bool {
if !self.match_token(TokenType::Identifier) {
return false;
}
if self.token_value(&self.current_token) != utf16!("using") {
return false;
}
let next = self.next_token();
if next.trivia_has_line_terminator {
return false;
}
if next.token_type == TokenType::Identifier {
let next_val = self.token_original_value(&next);
if next_val == utf16!("of") {
return false;
}
}
// Must be an actual identifier, not just an identifier-name keyword
// like `in`. This matches C++ token_is_identifier().
self.token_is_identifier(&next)
}
/// Validate that an expression-form LHS is valid for for-in/for-of.
fn validate_for_in_of_lhs(&mut self, init: &LocalForInit) {
if let LocalForInit::Expression(ref expression) = *init {
match &expression.inner {
// NewExpression is never a valid assignment target.
ExpressionKind::New(_) => {
self.syntax_error("Invalid left-hand side in for-loop");
}
// CallExpression is a valid assignment target only in non-strict mode (web compat).
ExpressionKind::Call(_) if self.flags.strict_mode => {
self.syntax_error("Invalid left-hand side in for-loop");
}
ExpressionKind::Identifier(_)
| ExpressionKind::Member(_)
| ExpressionKind::Call(_)
| ExpressionKind::Object(_)
| ExpressionKind::Array(_) => {}
_ => {
self.syntax_error("Invalid left-hand side in for-loop");
}
}
}
}
/// Convert a `LocalForInit` into a `ForInOfLhs`, synthesizing a binding
/// pattern when the LHS is an array or object expression.
fn synthesize_for_in_of_lhs(&mut self, init: LocalForInit, init_start: Position) -> ForInOfLhs {
match init {
LocalForInit::Declaration(declaration) => {
ForInOfLhs::Declaration(Box::new(declaration))
}
LocalForInit::Expression(expression) => {
if Self::is_array_expression(&expression) || Self::is_object_expression(&expression)
{
let pattern = self.synthesize_binding_pattern(init_start);
let bound_names: Vec<_> = self.pattern_bound_names.drain(..).collect();
for (name, id) in &bound_names {
self.check_identifier_name_for_assignment_validity(name, false);
self.scope_collector
.register_identifier(id.clone(), name, None);
}
ForInOfLhs::Pattern(pattern)
} else {
ForInOfLhs::Expression(Box::new(expression))
}
}
}
}
}