///|
fn add_lexical_decl_name(
lexical_names : @set.Set[String],
name : String,
) -> Unit raise Error {
if lexical_names.contains(name) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
}
lexical_names.add(name)
}
///|
fn collect_pattern_decl_names(
pattern : @ast.Pattern,
names : @set.Set[String],
lexical? : Bool = false,
) -> Unit raise Error {
for name in @static_semantics.bound_names(pattern) {
if lexical {
add_lexical_decl_name(names, name)
} else {
names.add(name)
}
}
}
///|
fn collect_block_lexical_decl_names_from_stmt(
stmt : @ast.Stmt,
lexical_names : @set.Set[String],
block_function_kinds : Map[String, Int],
include_block_functions? : Bool = false,
allow_duplicate_block_functions? : Bool = false,
) -> Unit raise Error {
match stmt {
VarDecl(LetKind, name, _, _) => add_lexical_decl_name(lexical_names, name)
VarDecl(ConstKind, name, _, _) => add_lexical_decl_name(lexical_names, name)
DestructureDecl(LetKind, pattern, _, _) =>
collect_pattern_decl_names(pattern, lexical_names, lexical=true)
DestructureDecl(ConstKind, pattern, _, _) =>
collect_pattern_decl_names(pattern, lexical_names, lexical=true)
ClassDecl(name, _, _, _, _) => add_lexical_decl_name(lexical_names, name)
// Function declarations are lexical only in block statement lists.
FuncDecl(name, _, _, _, _) =>
if include_block_functions {
if lexical_names.contains(name) {
if !(allow_duplicate_block_functions &&
block_function_kinds.get(name) == Some(1)) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
}
} else {
lexical_names.add(name)
}
block_function_kinds[name] = 1
}
FuncDeclExt(name, _, _, _, _, _) =>
if include_block_functions {
if lexical_names.contains(name) {
if !(allow_duplicate_block_functions &&
block_function_kinds.get(name) == Some(1)) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
}
} else {
lexical_names.add(name)
}
block_function_kinds[name] = 1
}
GeneratorDecl(name, _, _, _, _) =>
if include_block_functions {
if lexical_names.contains(name) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
} else {
lexical_names.add(name)
}
block_function_kinds[name] = 2
}
GeneratorDeclExt(name, _, _, _, _, _) =>
if include_block_functions {
if lexical_names.contains(name) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
} else {
lexical_names.add(name)
}
block_function_kinds[name] = 2
}
AsyncFuncDecl(name, _, _, _, _) =>
if include_block_functions {
if lexical_names.contains(name) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
} else {
lexical_names.add(name)
}
block_function_kinds[name] = 2
}
AsyncFuncDeclExt(name, _, _, _, _, _) =>
if include_block_functions {
if lexical_names.contains(name) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
} else {
lexical_names.add(name)
}
block_function_kinds[name] = 2
}
AsyncGeneratorDecl(name, _, _, _, _) =>
if include_block_functions {
if lexical_names.contains(name) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
} else {
lexical_names.add(name)
}
block_function_kinds[name] = 2
}
AsyncGeneratorDeclExt(name, _, _, _, _, _) =>
if include_block_functions {
if lexical_names.contains(name) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
} else {
lexical_names.add(name)
}
block_function_kinds[name] = 2
}
StmtList(inner_stmts, _) =>
for inner in inner_stmts {
collect_block_lexical_decl_names_from_stmt(
inner,
lexical_names,
block_function_kinds,
include_block_functions~,
allow_duplicate_block_functions~,
)
}
ExportNamedDecl(Some(decl), _, _, _, _) =>
collect_block_lexical_decl_names_from_stmt(
decl,
lexical_names,
block_function_kinds,
include_block_functions~,
allow_duplicate_block_functions~,
)
ExportDefaultDecl(expr, _) =>
match module_default_export_binding(expr) {
LocalDefaultBinding(name) => add_lexical_decl_name(lexical_names, name)
SyntheticDefaultBinding => ()
}
_ => ()
}
}
///|
fn validate_block_statement_list_early_errors(
stmts : Array[@ast.Stmt],
include_block_functions? : Bool = false,
allow_duplicate_block_functions? : Bool = false,
include_function_decls_in_var_names? : Bool = false,
) -> Unit raise Error {
let lexical_names = @set.Set::default()
let block_function_kinds : Map[String, Int] = Map([])
for stmt in stmts {
collect_block_lexical_decl_names_from_stmt(
stmt,
lexical_names,
block_function_kinds,
include_block_functions~,
allow_duplicate_block_functions~,
)
}
let var_name_set = @set.Set::default()
for
name in @static_semantics.collect_var_declared_names(
stmts,
include_funcs=include_function_decls_in_var_names,
) {
var_name_set.add(name)
}
for name in lexical_names {
if var_name_set.contains(name) {
raise @errors.SyntaxError(
message="Identifier '\{name}' has already been declared",
)
}
}
}
///|
/// CaseBlock declarations share one lexical environment even though the AST
/// stores each case body separately.
fn flatten_case_block_statements(
cases : Array[@ast.SwitchCase],
) -> Array[@ast.Stmt] {
let stmts : Array[@ast.Stmt] = []
for case in cases {
for stmt in case.body {
stmts.push(stmt)
}
}
stmts
}
///|
fn validate_strict_lex_form(
form : @token.LexForm,
loc : @token.Loc,
strict_context : Bool,
) -> Unit raise Error {
if !strict_context {
return
}
match form {
LexNormal => ()
StringLegacyOctalEscape =>
raise @errors.SyntaxError(
message="Octal escape sequences are not allowed in strict mode at line \{loc.line}, col \{loc.col}",
)
NumberLegacyOctalInt =>
raise @errors.SyntaxError(
message="Octal literals are not allowed in strict mode at line \{loc.line}, col \{loc.col}",
)
NumberNonOctalDecimalInt =>
raise @errors.SyntaxError(
message="Decimals with leading zeros are not allowed in strict mode at line \{loc.line}, col \{loc.col}",
)
}
}
///|
priv enum EarlyErrorTask {
Expr(@ast.Expr, Bool)
Stmt(@ast.Stmt, Bool)
Pattern(@ast.Pattern, Bool, Bool)
Param(@ast.Param, Bool)
StmtListCheck(Array[@ast.Stmt], Bool, Bool, Bool)
LexForm(@token.LexForm, @token.Loc, Bool)
TemplateQuasis(Array[(String, String?)], @token.Loc)
}
///|
fn schedule_exprs_reverse(
work : Array[EarlyErrorTask],
exprs : Array[@ast.Expr],
strict_context : Bool,
) -> Unit {
for i = exprs.length() - 1; i >= 0; i = i - 1 {
work.push(Expr(exprs[i], strict_context))
}
}
///|
fn schedule_stmts_reverse(
work : Array[EarlyErrorTask],
stmts : Array[@ast.Stmt],
strict_context : Bool,
) -> Unit {
for i = stmts.length() - 1; i >= 0; i = i - 1 {
work.push(Stmt(stmts[i], strict_context))
}
}
///|
fn schedule_params_reverse(
work : Array[EarlyErrorTask],
params : Array[@ast.Param],
strict_context : Bool,
) -> Unit {
for i = params.length() - 1; i >= 0; i = i - 1 {
work.push(Param(params[i], strict_context))
}
}
///|
fn schedule_function_body(
work : Array[EarlyErrorTask],
params : Array[@ast.Param]?,
body : Array[@ast.Stmt],
parent_strict : Bool,
) -> Unit {
let child_strict = is_function_strict(parent_strict, body)
schedule_stmts_reverse(work, body, child_strict)
match params {
Some(ps) => schedule_params_reverse(work, ps, child_strict)
None => ()
}
work.push(StmtListCheck(body, false, false, true))
}
///|
fn schedule_class_members_reverse(
work : Array[EarlyErrorTask],
members : Array[@ast.ClassMember],
) -> Unit {
for i = members.length() - 1; i >= 0; i = i - 1 {
match members[i] {
@ast.ClassMember::Method(m) => {
work.push(Expr(m.value, true))
work.push(Expr(m.key, true))
}
@ast.ClassMember::Field(f) => {
match f.initializer {
Some(expr) => work.push(Expr(expr, true))
None => ()
}
work.push(Expr(f.key, true))
}
@ast.ClassMember::StaticBlock(stmts, _) => {
schedule_stmts_reverse(work, stmts, true)
work.push(StmtListCheck(stmts, false, false, false))
}
}
}
}
///|
pub fn Interpreter::validate_block_early_errors(
_self : Interpreter,
stmts : Array[@ast.Stmt],
strict_context : Bool,
) -> Unit raise Error {
let work : Array[EarlyErrorTask] = []
schedule_stmts_reverse(work, stmts, strict_context)
work.push(StmtListCheck(stmts, false, false, true))
while work.length() > 0 {
let task = work.pop().unwrap()
match task {
StmtListCheck(
stmts,
include_funcs,
allow_duplicates,
include_funcs_in_var
) =>
validate_block_statement_list_early_errors(
stmts,
include_block_functions=include_funcs,
allow_duplicate_block_functions=allow_duplicates,
include_function_decls_in_var_names=include_funcs_in_var,
)
LexForm(form, loc, strict) => validate_strict_lex_form(form, loc, strict)
TemplateQuasis(quasis, loc) =>
for q in quasis {
if q.1 is None {
raise @errors.SyntaxError(
message="Invalid escape sequence in template literal at line \{loc.line}, col \{loc.col}",
)
}
}
Param(param, strict) => {
match param.pattern {
Some(pattern) => work.push(Pattern(pattern, strict, true))
None => ()
}
match param.default_val {
Some(expr) => work.push(Expr(expr, strict))
None => ()
}
}
Pattern(pattern, strict, binding) =>
match pattern {
IdentPat(name) =>
if binding {
if strict {
@static_semantics.validate_strict_binding_name(name)
}
} else {
@static_semantics.validate_strict_assignment_target_name(
strict, name,
)
}
DefaultPat(inner, default_expr) => {
work.push(Expr(default_expr, strict))
work.push(Pattern(inner, strict, binding))
}
AssignTarget(expr) =>
match expr {
Ident(name, _) =>
@static_semantics.validate_strict_assignment_target_name(
strict, name,
)
_ => work.push(Expr(expr, strict))
}
ArrayPat(elements, rest) => {
match rest {
Some(pattern) => work.push(Pattern(pattern, strict, binding))
None => ()
}
for i = elements.length() - 1; i >= 0; i = i - 1 {
match elements[i] {
Some(pattern) => work.push(Pattern(pattern, strict, binding))
None => ()
}
}
}
ObjectPat(props, rest) => {
match rest {
Some(pattern) => work.push(Pattern(pattern, strict, binding))
None => ()
}
for i = props.length() - 1; i >= 0; i = i - 1 {
let prop = props[i]
match prop.default_val {
Some(expr) => work.push(Expr(expr, strict))
None => ()
}
work.push(Pattern(prop.value, strict, binding))
match prop.computed_key {
Some(expr) => work.push(Expr(expr, strict))
None => ()
}
work.push(LexForm(prop.key_lex_form, prop.key_loc, strict))
}
}
}
Expr(expr, strict) =>
match expr {
Ident(name, _) =>
@static_semantics.validate_strict_identifier_reference(strict, name)
StringLit(_, _, form, loc) | NumberLit(_, form, loc) =>
validate_strict_lex_form(form, loc, strict)
FuncExpr(_, _, body, _, _)
| ArrowFunc(_, body, _, _)
| GeneratorExpr(_, _, body, _, _)
| AsyncFuncExpr(_, _, body, _, _)
| AsyncArrowFunc(_, body, _, _)
| AsyncGeneratorExpr(_, _, body, _, _) =>
schedule_function_body(work, None, body, strict)
FuncExprExt(_, params, _, body, _, _)
| GeneratorExprExt(_, params, _, body, _, _)
| AsyncFuncExprExt(_, params, _, body, _, _)
| AsyncGeneratorExprExt(_, params, _, body, _, _) =>
schedule_function_body(work, Some(params), body, strict)
ArrowFuncExt(params, _, body, _, _)
| AsyncArrowFuncExt(params, _, body, _, _) =>
schedule_function_body(work, Some(params), body, strict)
ClassExpr(_, superclass, members, _, _) => {
schedule_class_members_reverse(work, members)
match superclass {
Some(expr) => work.push(Expr(expr, true))
None => ()
}
}
Assign(name, rhs, _) => {
work.push(Expr(rhs, strict))
@static_semantics.validate_strict_assignment_target_name(
strict, name,
)
}
UpdateExpr(_, target, _, _) =>
match target {
Ident(name, _) =>
@static_semantics.validate_strict_assignment_target_name(
strict, name,
)
_ => work.push(Expr(target, strict))
}
CompoundAssign(_, lhs, rhs, _) => {
work.push(Expr(rhs, strict))
match lhs {
Ident(name, _) =>
@static_semantics.validate_strict_assignment_target_name(
strict, name,
)
_ => work.push(Expr(lhs, strict))
}
}
Binary(_, left, right, _) | Comma(left, right, _) => {
work.push(Expr(right, strict))
work.push(Expr(left, strict))
}
ComputedMember(obj, key, _)
| OptionalComputedMember(obj, key, _)
| ChainComputedMember(obj, key, _) => {
work.push(Expr(key, strict))
work.push(Expr(obj, strict))
}
MemberAssign(obj, _, rhs, _) => {
work.push(Expr(rhs, strict))
work.push(Expr(obj, strict))
}
ComputedAssign(obj, key, rhs, _) => {
work.push(Expr(rhs, strict))
work.push(Expr(key, strict))
work.push(Expr(obj, strict))
}
SuperComputedAssign(key, rhs, _) => {
work.push(Expr(rhs, strict))
work.push(Expr(key, strict))
}
Ternary(cond, yes, no, _) => {
work.push(Expr(no, strict))
work.push(Expr(yes, strict))
work.push(Expr(cond, strict))
}
Call(callee, args, _)
| NewExpr(callee, args, _)
| OptionalCall(callee, args, _) => {
schedule_exprs_reverse(work, args, strict)
work.push(Expr(callee, strict))
}
ObjectLit(props, _) =>
for i = props.length() - 1; i >= 0; i = i - 1 {
work.push(Expr(props[i].value, strict))
work.push(Expr(props[i].key, strict))
}
ArrayLit(elements, _) | SuperCall(elements, _) =>
schedule_exprs_reverse(work, elements, strict)
TemplateLit(quasis, exprs, loc) => {
schedule_exprs_reverse(work, exprs, strict)
work.push(TemplateQuasis(quasis, loc))
}
DestructureAssign(pattern, rhs, _) => {
work.push(Expr(rhs, strict))
work.push(Pattern(pattern, strict, false))
}
TaggedTemplate(tag, _, exprs, _) => {
schedule_exprs_reverse(work, exprs, strict)
work.push(Expr(tag, strict))
}
Unary(_, inner, _)
| Grouping(inner, _)
| SpreadExpr(inner, _)
| AwaitExpr(inner, _)
| Member(inner, _, _)
| OptionalMember(inner, _, _)
| ChainMember(inner, _, _)
| SuperComputedMember(inner, _)
| SuperMemberAssign(_, inner, _) => work.push(Expr(inner, strict))
YieldExpr(arg, _, _) =>
match arg {
Some(inner) => work.push(Expr(inner, strict))
None => ()
}
BoolLit(_, _)
| NullLit(_)
| UndefinedLit(_)
| ArrayHole(_)
| ThisExpr(_)
| RegexLit(_, _, _)
| SuperMember(_, _)
| NewTargetExpr(_)
| WebCompatCallAssign(_, _, _)
| PrivateIdent(_, _)
| PrivateMember(_, _, _)
| PrivateMemberAssign(_, _, _, _) => ()
}
Stmt(stmt, strict) =>
match stmt {
Block(inner, _) => {
schedule_stmts_reverse(work, inner, strict)
work.push(StmtListCheck(inner, true, !strict, false))
}
StmtList(inner, _) => schedule_stmts_reverse(work, inner, strict)
ExprStmt(expr, _) | ThrowStmt(expr, _) | ExportDefaultDecl(expr, _) =>
work.push(Expr(expr, strict))
VarDecl(_, name, init, _) => {
match init {
Some(expr) => work.push(Expr(expr, strict))
None => ()
}
if strict {
@static_semantics.validate_strict_binding_name(name)
}
}
DestructureDecl(_, pattern, init, _) => {
work.push(Expr(init, strict))
work.push(Pattern(pattern, strict, true))
}
IfStmt(cond, yes, no, _) => {
match no {
Some(stmt) => work.push(Stmt(stmt, strict))
None => ()
}
work.push(Stmt(yes, strict))
work.push(Expr(cond, strict))
}
WhileStmt(cond, body, _) => {
work.push(Stmt(body, strict))
work.push(Expr(cond, strict))
}
DoWhileStmt(body, cond, _) => {
work.push(Expr(cond, strict))
work.push(Stmt(body, strict))
}
ForStmt(init, cond, update, body, _) => {
work.push(Stmt(body, strict))
match update {
Some(e) => work.push(Expr(e, strict))
None => ()
}
match cond {
Some(e) => work.push(Expr(e, strict))
None => ()
}
match init {
Some(s) => work.push(Stmt(s, strict))
None => ()
}
}
ForInStmt(kind, name, expr, body, _)
| ForOfStmt(kind, name, expr, body, _)
| AsyncForOfStmt(kind, name, expr, body, _) => {
work.push(Stmt(body, strict))
work.push(Expr(expr, strict))
if kind is Some(_) {
if strict {
@static_semantics.validate_strict_binding_name(name)
}
} else {
@static_semantics.validate_strict_assignment_target_name(
strict, name,
)
}
}
ForInStmtPat(kind, pattern, expr, body, _)
| ForOfStmtPat(kind, pattern, expr, body, _)
| AsyncForOfStmtPat(kind, pattern, expr, body, _) => {
work.push(Stmt(body, strict))
work.push(Expr(expr, strict))
work.push(Pattern(pattern, strict, kind is Some(_)))
}
ForInExpr(lhs, expr, body, _)
| ForOfExpr(lhs, expr, body, _)
| AsyncForOfExpr(lhs, expr, body, _) => {
work.push(Stmt(body, strict))
work.push(Expr(expr, strict))
match lhs {
Ident(name, _) =>
@static_semantics.validate_strict_assignment_target_name(
strict, name,
)
_ => work.push(Expr(lhs, strict))
}
}
LabeledStmt(_, inner, _) => work.push(Stmt(inner, strict))
ReturnStmt(expr, _) =>
match expr {
Some(e) => work.push(Expr(e, strict))
None => ()
}
SwitchStmt(discriminant, cases, _) => {
for i = cases.length() - 1; i >= 0; i = i - 1 {
schedule_stmts_reverse(work, cases[i].body, strict)
match cases[i].condition {
Some(expr) => work.push(Expr(expr, strict))
None => ()
}
}
work.push(
StmtListCheck(
flatten_case_block_statements(cases),
true,
!strict,
false,
),
)
work.push(Expr(discriminant, strict))
}
TryCatchStmt(try_body, catch_param, catch_body, finally_body, _) => {
match finally_body {
Some(xs) => schedule_stmts_reverse(work, xs, strict)
None => ()
}
match catch_body {
Some(xs) => schedule_stmts_reverse(work, xs, strict)
None => ()
}
match catch_param {
Some(p) => work.push(Pattern(p, strict, true))
None => ()
}
schedule_stmts_reverse(work, try_body, strict)
}
FuncDecl(_, _, body, _, _)
| GeneratorDecl(_, _, body, _, _)
| AsyncFuncDecl(_, _, body, _, _)
| AsyncGeneratorDecl(_, _, body, _, _) =>
schedule_function_body(work, None, body, strict)
FuncDeclExt(_, params, _, body, _, _)
| GeneratorDeclExt(_, params, _, body, _, _)
| AsyncFuncDeclExt(_, params, _, body, _, _)
| AsyncGeneratorDeclExt(_, params, _, body, _, _) =>
schedule_function_body(work, Some(params), body, strict)
ClassDecl(_, superclass, members, _, _) => {
schedule_class_members_reverse(work, members)
match superclass {
Some(expr) => work.push(Expr(expr, true))
None => ()
}
}
WithStmt(obj, body, _) => {
if strict {
raise @errors.SyntaxError(
message="Strict mode code may not include a with statement",
)
}
work.push(Stmt(body, strict))
work.push(Expr(obj, strict))
}
ImportDecl(_, _, _, _, form, loc) | ExportAllDecl(_, _, form, loc) =>
validate_strict_lex_form(form, loc, strict)
ExportNamedDecl(decl, _, _, form, loc) => {
match decl {
Some(stmt) => work.push(Stmt(stmt, strict))
None => ()
}
validate_strict_lex_form(form, loc, strict)
}
BreakStmt(_, _) | ContinueStmt(_, _) => ()
}
}
}
}
///|
fn collect_eval_var_names(stmts : Array[@ast.Stmt]) -> Array[String] {
@static_semantics.collect_var_declared_names(stmts)
}
///|
fn hoist_pattern(pattern : @ast.Pattern, env : Environment) -> Unit raise Error {
for name in @static_semantics.bound_names(pattern) {
if !env.bindings.contains(name) {
env.def(name, Undefined, VarBinding)
}
}
}
///|
/// Read-only mirror of hoist_var_declarations_from_stmt — walks the same
/// VarScopedDeclarations tree and invokes on_name for every var-scoped binding
/// without creating any bindings. Does not recurse into function/class/generator/
/// async-function bodies (separate var scopes per ES §8.2.4).
fn walk_var_scoped_names(
stmt : @ast.Stmt,
on_name : (String) -> Unit raise Error,
) -> Unit raise Error {
for
name in @static_semantics.collect_var_declared_names(
[stmt],
include_funcs=false,
) {
on_name(name)
}
}
///|
fn hoist_var_declarations_from_stmt(
stmt : @ast.Stmt,
env : Environment,
) -> Unit raise Error {
walk_var_scoped_names(stmt, name => {
if !env.bindings.contains(name) {
env.def(name, Undefined, VarBinding)
}
})
}
///|
/// Annex B §B.3.3.3: record a block-level function as an eligible var binding
/// on `var_env`. Creates a new `VarBinding` initialized to Undefined if absent,
/// or simply tags an existing compatible binding (including a parameter stored
/// as `LetBinding`) so that the runtime block-entry reinit in `exec_stmt.mbt`
/// is allowed to update it.
fn tag_annex_b_hoisted(
interp : Interpreter,
var_env : Environment,
name : String,
) -> Unit {
match var_env.bindings.get(name) {
// §B.3.4 skip check: the extension only applies if replacing the
// FunctionDeclaration with `var name` would not be an Early Error. A
// pre-existing let/const/class in the var env makes `var name` illegal,
// so we must not tag — otherwise the later block-entry reinit would
// overwrite an immutable lexical binding via direct `binding.value = ...`
// (bypassing `Environment::assign` const enforcement). Parameters share
// `BindingKind::LetBinding` with real `let` in the same environment
// (when there are no param defaults), so `is_parameter` is the only
// runtime-distinguishable signal.
Some(existing) =>
if existing.kind == VarBinding || existing.is_parameter {
existing.annex_b_hoisted = true
}
None => {
var_env.bindings[name] = {
value: Undefined,
kind: VarBinding,
initialized: true,
annex_b_hoisted: true,
is_parameter: false,
}
// Mirror to globalThis when the var env IS the global env — matches
// the normal hoist path in `hoist_declarations`.
if physical_equal(var_env, interp.global) {
interp.mirror_to_global(name, Undefined)
}
}
}
}
///|
/// Add `let` / `const` / `class` names introduced by a single statement to
/// `names`. Non-recursive — blocks, function bodies, loop bodies, etc. are
/// NOT descended into; the caller controls scope granularity. The
/// `ExportNamedDecl` wrapper is unwrapped so `export let x = …` participates.
/// `StmtList` IS descended into: the parser emits it as a flat carrier for
/// comma-separated declarations (`let f=1, g=2` → `StmtList([VarDecl,
/// VarDecl])`), not a new scope, so its inner decls belong to the same lex
/// frame as the surrounding statements.
fn add_stmt_lex_names(
stmt : @ast.Stmt,
names : @set.Set[String],
) -> Unit raise Error {
match stmt {
VarDecl(LetKind, name, _, _) | VarDecl(ConstKind, name, _, _) =>
names.add(name)
ClassDecl(name, _, _, _, _) => names.add(name)
DestructureDecl(LetKind, pattern, _, _)
| DestructureDecl(ConstKind, pattern, _, _) =>
collect_pattern_lex_names(pattern, names)
StmtList(inner_stmts, _) =>
for inner in inner_stmts {
add_stmt_lex_names(inner, names)
}
ExportNamedDecl(Some(decl), _, _, _, _) => add_stmt_lex_names(decl, names)
_ => ()
}
}
///|
/// Walk a pattern and record every `IdentPat` name into `names`. Used for
/// `let` / `const` destructuring declarations and for-loop heads.
fn collect_pattern_lex_names(
pattern : @ast.Pattern,
names : @set.Set[String],
) -> Unit {
for name in @static_semantics.bound_names(pattern) {
names.add(name)
}
}
///|
/// Collect lexically-declared names (§8.2.10 TopLevelLexicallyDeclaredNames)
/// from a statement list — `let` / `const` / `class` at the given scope, with
/// no recursion into Block, function body, switch case, loop body, catch
/// block, or `with` body. Suitable for both eval top level (§B.3.3.3 skip
/// check) and for per-block lex frames pushed onto the AST walk stack.
fn collect_stmts_lex_names(
stmts : Array[@ast.Stmt],
) -> @set.Set[String] raise Error {
let names = @set.Set::default()
for stmt in stmts {
add_stmt_lex_names(stmt, names)
}
names
}
///|
/// Annex B §B.3.3.3 skip check: true iff replacing a `function F` nested in a
/// block/switch/loop-body with `var F` would produce an early error — i.e. `F`
/// is a lexically-declared name somewhere between the candidate site and the
/// eval var env.
///
/// Inputs:
/// - `name` — the candidate function name.
/// - `top_lex` — eval body's top-level lex names (let/const/class).
/// - `lex_stack` — stack of lex-name frames pushed as the AST walk enters
/// enclosing syntactic lex scopes (Block, for-head, switch case-block).
/// Each frame collects names introduced AT that scope (not inherited).
///
/// Per Codex review (§B.3.4), the catch binding is transparent for eval
/// conflict checks, so catch parameters are NOT pushed onto `lex_stack`.
/// Per §19.2.1.3 step 3.d, Object Environment Records (`with` bodies) are
/// ignored — `with` pushes nothing.
fn annex_b_candidate_conflicts(
name : String,
top_lex : @set.Set[String],
lex_stack : Array[@set.Set[String]],
excluded? : @set.Set[String] = @set.Set::default(),
) -> Bool {
// §B.3.2.1 step ii parameter carve-out: when called from a function body,
// `excluded` contains formal parameter names (plus "arguments" when the
// arguments object exists). Per the spec, those are NOT candidates —
// the extension is skipped so the param / arguments binding survives.
// Eval (§B.3.2.3) passes `excluded` empty; SetMutableBinding in step
// 10.d.iii permits overwrites, matching current behavior.
if excluded.contains(name) {
return true
}
if top_lex.contains(name) {
return true
}
for frame in lex_stack {
if frame.contains(name) {
return true
}
} nobreak {
false
}
}
///|
/// Collect the lex names introduced at the top level of a single block's
/// statement list: let, const, class only.
///
/// Annex B §B.3.3.3: walk an eval body AST and hoist each eligible
/// block-level function declaration as a var binding on `var_env`.
///
/// The walk maintains a syntactic lex-scope stack. When entering a `Block`,
/// the block's own lex names are pushed; popped on exit. Loop-head lex
/// decls, switch-case scopes, finally bodies, and labeled/if branches follow
/// the same pattern. Function and class bodies are NOT traversed (they have
/// their own hoist pass when called).
///
/// For each candidate `function F` found at a block-nested position:
/// - If `F` conflicts per `annex_b_candidate_conflicts`, SKIP the extension.
/// - Otherwise tag `F` on `var_env` via `tag_annex_b_hoisted`.
fn hoist_eval_annex_b_candidates(
interp : Interpreter,
stmts : Array[@ast.Stmt],
var_env : Environment,
top_lex : @set.Set[String],
excluded? : @set.Set[String] = @set.Set::default(),
) -> Unit raise Error {
let lex_stack : Array[@set.Set[String]] = []
let work : Array[AnnexBWalkTask] = []
schedule_annex_b_stmts_reverse(work, stmts, in_block=false)
while work.pop() is Some(task) {
match task {
VisitAnnexBStmt(stmt, in_block) =>
visit_eval_annex_b_stmt(
interp,
stmt,
var_env,
top_lex,
lex_stack,
work,
in_block~,
excluded~,
)
EnterAnnexBStmtList(inner_stmts) => {
lex_stack.push(collect_stmts_lex_names(inner_stmts))
work.push(LeaveAnnexBLexicalFrame)
schedule_annex_b_stmts_reverse(work, inner_stmts, in_block=true)
}
EnterAnnexBCatch(catch_param, catch_body) => {
let catch_frame = @set.Set::default()
match catch_param {
Some(IdentPat(_)) => ()
Some(pattern) =>
for name in @static_semantics.bound_names(pattern) {
catch_frame.add(name)
}
None => ()
}
lex_stack.push(catch_frame)
work.push(LeaveAnnexBLexicalFrame)
work.push(EnterAnnexBStmtList(catch_body))
}
LeaveAnnexBLexicalFrame => {
guard lex_stack.pop() is Some(_) else {
fail("unbalanced Annex B lexical-frame continuation")
}
}
}
}
}
///|
priv enum AnnexBWalkTask {
VisitAnnexBStmt(@ast.Stmt, Bool)
EnterAnnexBStmtList(Array[@ast.Stmt])
EnterAnnexBCatch(@ast.Pattern?, Array[@ast.Stmt])
LeaveAnnexBLexicalFrame
}
///|
fn schedule_annex_b_stmts_reverse(
work : Array[AnnexBWalkTask],
stmts : Array[@ast.Stmt],
in_block~ : Bool,
) -> Unit {
for i = stmts.length() - 1; i >= 0; i = i - 1 {
work.push(VisitAnnexBStmt(stmts[i], in_block))
}
}
///|
fn schedule_annex_b_enclosed_body(
work : Array[AnnexBWalkTask],
lex_stack : Array[@set.Set[String]],
body : @ast.Stmt,
frame : @set.Set[String],
) -> Unit {
lex_stack.push(frame)
work.push(LeaveAnnexBLexicalFrame)
work.push(VisitAnnexBStmt(body, true))
}
///|
fn visit_eval_annex_b_stmt(
interp : Interpreter,
stmt : @ast.Stmt,
var_env : Environment,
top_lex : @set.Set[String],
lex_stack : Array[@set.Set[String]],
work : Array[AnnexBWalkTask],
in_block~ : Bool,
excluded~ : @set.Set[String],
) -> Unit raise Error {
match stmt {
FuncDecl(name, _, _, _, _) | FuncDeclExt(name, _, _, _, _, _) =>
if in_block &&
!annex_b_candidate_conflicts(name, top_lex, lex_stack, excluded~) {
tag_annex_b_hoisted(interp, var_env, name)
}
Block(inner_stmts, _) => work.push(EnterAnnexBStmtList(inner_stmts))
StmtList(inner_stmts, _) =>
schedule_annex_b_stmts_reverse(work, inner_stmts, in_block~)
// §B.3.4: `if (expr) FunctionDeclaration` is treated as if the body
// were `{ FunctionDeclaration }`. Each branch is a block context for
// Annex B, so FuncDecls under IfStmt ARE candidates even at the eval
// top level. If the branch is itself a Block, the Block arm pushes its
// own frame — no double-pushing.
IfStmt(_, then_branch, else_branch, _) => {
match else_branch {
Some(else_stmt) => work.push(VisitAnnexBStmt(else_stmt, true))
None => ()
}
work.push(VisitAnnexBStmt(then_branch, true))
}
WhileStmt(_, body, _) => work.push(VisitAnnexBStmt(body, true))
DoWhileStmt(body, _, _) => work.push(VisitAnnexBStmt(body, true))
// §14.7.4: `for (let/const …; ; ) body` makes the init's lex names visible
// for the body's Annex-B candidate check.
ForStmt(init, _, _, body, _) =>
schedule_annex_b_enclosed_body(
work,
lex_stack,
body,
for_init_lex_names(init),
)
ForInStmt(kind, name, _, body, _) => {
let frame = @set.Set::default()
if kind is (Some(LetKind) | Some(ConstKind)) {
frame.add(name)
}
schedule_annex_b_enclosed_body(work, lex_stack, body, frame)
}
ForOfStmt(kind, name, _, body, _)
| AsyncForOfStmt(kind, name, _, body, _) => {
let frame = @set.Set::default()
if kind is (Some(LetKind) | Some(ConstKind)) {
frame.add(name)
}
schedule_annex_b_enclosed_body(work, lex_stack, body, frame)
}
ForInStmtPat(kind, pattern, _, body, _) => {
let frame = @set.Set::default()
if kind is (Some(LetKind) | Some(ConstKind)) {
collect_pattern_lex_names(pattern, frame)
}
schedule_annex_b_enclosed_body(work, lex_stack, body, frame)
}
ForOfStmtPat(kind, pattern, _, body, _)
| AsyncForOfStmtPat(kind, pattern, _, body, _) => {
let frame = @set.Set::default()
if kind is (Some(LetKind) | Some(ConstKind)) {
collect_pattern_lex_names(pattern, frame)
}
schedule_annex_b_enclosed_body(work, lex_stack, body, frame)
}
ForInExpr(_, _, body, _) => work.push(VisitAnnexBStmt(body, true))
ForOfExpr(_, _, body, _) | AsyncForOfExpr(_, _, body, _) =>
work.push(VisitAnnexBStmt(body, true))
// §B.3.3 + §14.13: a `label:`-wrapped FuncDecl is an Annex B candidate
// just like a plain block FuncDecl. Mirror the IfStmt treatment — the
// inner is a block context, so pass `in_block=true`.
LabeledStmt(_, inner, _) => work.push(VisitAnnexBStmt(inner, true))
// §14.12.3: all case clauses share one lex scope. Frame excludes
// FuncDecls for the same reason `collect_stmts_lex_names` does — a
// candidate `function F` in one case must not self-conflict. Duplicate
// lex declarations across cases are caught by the early-error validator.
SwitchStmt(_, cases, _) => {
let frame = @set.Set::default()
for case_ in cases {
for inner in case_.body {
add_stmt_lex_names(inner, frame)
}
}
lex_stack.push(frame)
work.push(LeaveAnnexBLexicalFrame)
for case_index = cases.length() - 1
case_index >= 0
case_index = case_index - 1 {
schedule_annex_b_stmts_reverse(
work,
cases[case_index].body,
in_block=true,
)
}
}
// §B.3.5 splits catch parameters in two: a simple BindingIdentifier
// `catch (f) { var f; }` is exempt and lets `f` flow through to the
// surrounding var scope, but a destructuring pattern like
// `catch ({ f }) { ... }` does NOT get the exemption — replacing the
// candidate `function f(){}` with `var f` would be a §B.3.5 SyntaxError,
// so §B.3.3.3 step 2.c.i requires we skip the extension. Push the
// catch's bound names as a lex frame around the catch body ONLY when
// the parameter is destructuring; an IdentPat catch param stays
// transparent. try-body and finally-body are always unaffected — only
// catch-body inherits the parameter binding.
TryCatchStmt(try_body, catch_param, catch_body, finally_body, _) => {
match finally_body {
Some(finally_stmts) => work.push(EnterAnnexBStmtList(finally_stmts))
None => ()
}
match catch_body {
Some(catch_stmts) =>
work.push(EnterAnnexBCatch(catch_param, catch_stmts))
None => ()
}
work.push(EnterAnnexBStmtList(try_body))
}
WithStmt(_, body, _) =>
// Object env record — no lex frame pushed.
work.push(VisitAnnexBStmt(body, true))
ExportNamedDecl(Some(decl), _, _, _, _) =>
work.push(VisitAnnexBStmt(decl, in_block))
// Function / class bodies: do NOT traverse. They have their own B.3.3
// hoist on call (§B.3.2.1), operating in a fresh scope.
_ => ()
}
}
///|
/// Extract lex (let/const) names introduced by a for-loop init. An init of
/// `StmtList(inner, _)` is the comma-separated `for (let a=1, b=2; …)` shape
/// — unwrap it one level so each inner declaration contributes.
fn for_init_lex_names(init : @ast.Stmt?) -> @set.Set[String] raise Error {
let names = @set.Set::default()
match init {
Some(StmtList(inner, _)) =>
for s in inner {
add_stmt_lex_names(s, names)
}
Some(init_stmt) => add_stmt_lex_names(init_stmt, names)
None => ()
}
names
}
///|
/// Hoist a body's var / function / let / const / class declarations into
/// `env`. When the caller has split §10.2.11 function scope into a distinct
/// `param_env` (holding formal params + `arguments`) and a `body_env`
/// (passed as `env` here), the caller supplies `param_source=Some(param_env)`
/// so the Annex B §B.3.2.1 skip check's exclusion set is populated from the
/// param env's `is_parameter` bindings rather than from `env` (which has
/// none in the split case). Scripts, modules, and non-Ext single-env call
/// paths omit `param_source` and keep scanning `env`.
fn Interpreter::hoist_declarations(
_self : Interpreter,
stmts : Array[@ast.Stmt],
env : Environment,
strict? : Bool = false,
suppress_annex_b_candidates? : Bool = false,
param_source? : Environment? = None,
) -> Unit raise Error {
let is_global = physical_equal(env, _self.global)
// §15.1.11 GlobalDeclarationInstantiation pre-check: verify all declarations
// are definable before creating any bindings (spec steps 9-12). This ensures
// TypeError is thrown atomically with no partial binding creation.
if is_global {
match _self.global_this {
Object(data) => {
let declared_function_names : @set.Set[String] = @set.Set::default()
// Steps 9-10: CanDeclareGlobalFunction for each function declaration
for pre_stmt in stmts {
let fname : String? = match pre_stmt {
FuncDecl(name, _, _, _, _) => Some(name)
FuncDeclExt(name, _, _, _, _, _) => Some(name)
GeneratorDecl(name, _, _, _, _) => Some(name)
GeneratorDeclExt(name, _, _, _, _, _) => Some(name)
AsyncFuncDecl(name, _, _, _, _) => Some(name)
AsyncFuncDeclExt(name, _, _, _, _, _) => Some(name)
AsyncGeneratorDecl(name, _, _, _, _) => Some(name)
AsyncGeneratorDeclExt(name, _, _, _, _, _) => Some(name)
_ => None
}
match fname {
Some(name) =>
if !declared_function_names.contains(name) {
// §9.1.1.4.16 CanDeclareGlobalFunction
let definable = match
ordinary_get_own_string_desc(data.bag, name) {
None => data.extensible
Some(desc) =>
desc.configurable ||
(!desc.is_accessor && desc.writable && desc.enumerable)
}
if !definable {
raise @errors.TypeError(
message="Cannot declare global function '\{name}': property is non-configurable",
)
}
declared_function_names.add(name) |> ignore
}
None => ()
}
}
// Steps 11-12: CanDeclareGlobalVar for all var-scoped names. Uses the
// same recursive VarScopedDeclarations walk as the actual hoist so that
// nested vars (inside blocks, ifs, loops, switch, try-catch) are checked.
for pre_stmt in stmts {
walk_var_scoped_names(pre_stmt, name => {
if !declared_function_names.contains(name) {
// §9.1.1.4.15 CanDeclareGlobalVar
let definable = ordinary_get_own_string_desc(data.bag, name)
is Some(_) ||
data.extensible
if !definable {
raise @errors.TypeError(
message="Cannot declare global var '\{name}': global object is non-extensible",
)
}
}
})
}
}
_ => ()
}
}
for stmt in stmts {
match stmt {
VarDecl(VarKind, name, _, _) =>
if !env.bindings.contains(name) {
env.def(name, Undefined, VarBinding)
// Mirror var declaration to global object per ES spec
if is_global {
_self.mirror_to_global(name, Undefined)
}
}
FuncDecl(name, params, body, _, source_text) => {
validate_function_signature(strict, Some(name), params, body)
let func_data : FuncData = {
name: Some(name),
params,
body,
closure: env,
strict: is_function_strict(strict, body),
// FunctionDeclarations don't get a §15.2.5 self-name binding:
// the name is hoisted into the enclosing VarEnvironment instead.
has_name_binding: false,
is_method: false,
source_text,
}
let func_val = make_func(func_data)
if env.bindings.contains(name) {
env.assign(name, func_val)
} else {
env.def(name, func_val, VarBinding)
}
// Mirror function declaration to global object per ES spec
if is_global {
_self.mirror_to_global(name, func_val)
}
}
FuncDeclExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
strict,
Some(name),
params,
rest_param,
body,
)
let func_data : FuncDataExt = {
name: Some(name),
params,
rest_param,
body,
closure: env,
strict: is_function_strict(strict, body),
has_name_binding: false,
is_method: false,
source_text,
}
let func_val = make_func_ext(func_data)
if env.bindings.contains(name) {
env.assign(name, func_val)
} else {
env.def(name, func_val, VarBinding)
}
// Mirror function declaration to global object per ES spec
if is_global {
_self.mirror_to_global(name, func_val)
}
}
GeneratorDecl(name, params, body, _, source_text) => {
validate_function_signature(strict, Some(name), params, body)
let gen_func = _self.make_generator_function(
Some(name),
params,
None,
body,
is_function_strict(strict, body),
env,
source_text~,
)
if env.bindings.contains(name) {
env.assign(name, gen_func)
} else {
env.def(name, gen_func, VarBinding)
}
// Mirror generator declaration to global object per ES spec
if is_global {
_self.mirror_to_global(name, gen_func)
}
}
GeneratorDeclExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
strict,
Some(name),
params,
rest_param,
body,
)
let gen_func = _self.make_generator_function_ext(
Some(name),
params,
rest_param,
body,
is_function_strict(strict, body),
env,
source_text~,
)
if env.bindings.contains(name) {
env.assign(name, gen_func)
} else {
env.def(name, gen_func, VarBinding)
}
// Mirror generator declaration to global object per ES spec
if is_global {
_self.mirror_to_global(name, gen_func)
}
}
AsyncFuncDecl(name, params, body, _, source_text) => {
validate_function_signature(strict, Some(name), params, body)
let async_func = _self.make_async_function(
Some(name),
params,
None,
body,
is_function_strict(strict, body),
env,
source_text~,
)
if env.bindings.contains(name) {
env.assign(name, async_func)
} else {
env.def(name, async_func, VarBinding)
}
}
AsyncFuncDeclExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
strict,
Some(name),
params,
rest_param,
body,
)
let async_func = _self.make_async_function_ext(
Some(name),
params,
rest_param,
body,
is_function_strict(strict, body),
env,
source_text~,
)
if env.bindings.contains(name) {
env.assign(name, async_func)
} else {
env.def(name, async_func, VarBinding)
}
}
AsyncGeneratorDecl(name, params, body, _, source_text) => {
validate_function_signature(strict, Some(name), params, body)
let gen_func = _self.make_async_generator_function(
Some(name),
params,
None,
body,
is_function_strict(strict, body),
env,
source_text~,
)
if env.bindings.contains(name) {
env.assign(name, gen_func)
} else {
env.def(name, gen_func, VarBinding)
}
}
AsyncGeneratorDeclExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
strict,
Some(name),
params,
rest_param,
body,
)
let gen_func = _self.make_async_generator_function_ext(
Some(name),
params,
rest_param,
body,
is_function_strict(strict, body),
env,
source_text~,
)
if env.bindings.contains(name) {
env.assign(name, gen_func)
} else {
env.def(name, gen_func, VarBinding)
}
}
DestructureDecl(VarKind, pattern, _, _) => hoist_pattern(pattern, env)
StmtList(inner_stmts, _) =>
// Recursively hoist declarations from StmtList (comma-separated decls)
for inner in inner_stmts {
match inner {
VarDecl(VarKind, name, _, _) =>
if !env.bindings.contains(name) {
env.def(name, Undefined, VarBinding)
}
DestructureDecl(VarKind, pattern, _, _) =>
hoist_pattern(pattern, env)
_ => ()
}
}
// Export with declaration: hoist the inner declaration
ExportNamedDecl(Some(decl), _, _, _, _) => {
let inner_stmts : Array[@ast.Stmt] = [decl]
_self.hoist_declarations(inner_stmts, env, strict~)
}
// export default function name() {} - hoist the named function
ExportDefaultDecl(FuncExpr(Some(name), params, body, _, source_text), _) => {
validate_function_signature(strict, Some(name), params, body)
let func_data : FuncData = {
name: Some(name),
params,
body,
closure: env,
strict: is_function_strict(strict, body),
// `export default function f() {}` is a HoistableDeclaration;
// the name is bound in the module env, not on a funcEnv.
has_name_binding: false,
is_method: false,
source_text,
}
if env.bindings.contains(name) {
env.assign(name, make_func(func_data))
} else {
env.def(name, make_func(func_data), VarBinding)
}
}
ExportDefaultDecl(
FuncExprExt(Some(name), params, rest_param, body, _, source_text),
_
) => {
validate_function_signature_ext(
strict,
Some(name),
params,
rest_param,
body,
)
let func_data : FuncDataExt = {
name: Some(name),
params,
rest_param,
body,
closure: env,
strict: is_function_strict(strict, body),
has_name_binding: false,
is_method: false,
source_text,
}
if env.bindings.contains(name) {
env.assign(name, make_func_ext(func_data))
} else {
env.def(name, make_func_ext(func_data), VarBinding)
}
}
_ => ()
}
// Hoist var declarations recursively through nested statements
// (e.g. var inside blocks still hoists to function/global var scope).
hoist_var_declarations_from_stmt(stmt, env)
}
// Annex B §B.3.2.1: Hoist block-level FunctionDeclarations into the
// containing function / script var env, with the syntactic skip check
// from §B.3.4 — tag only when the candidate name does not conflict with
// an enclosing lex-declared name. The same walker powers eval-scope
// hoisting (§B.3.2.3); eval callers suppress this pass and run it
// themselves in `perform_eval` so they can slot the call between var
// hoisting and TDZ setup.
if _self.annex_b && !strict && !suppress_annex_b_candidates {
let top_lex = collect_stmts_lex_names(stmts)
// §B.3.2.1 step ii parameter carve-out: skip the extension when the
// candidate name is a formal parameter of the enclosing function, or
// the distinguished name "arguments" (§10.2.11 step 22.f appends it to
// parameterNames). Populate `excluded` by scanning `env`: formal params
// are marked `is_parameter=true` by `Environment::def_parameter`, and
// `arguments` is installed as a VarBinding during function-call setup.
// Script / module top-level envs have no such bindings, so `excluded`
// stays empty and no names are skipped.
let excluded = @set.Set::default()
// In the split (§10.2.11) case, param/"arguments" bindings live on
// `param_source`, not `env` (= body_env). Fall back to `env` when no
// split (scripts/modules/non-Ext single-env paths).
let scan_env = match param_source {
Some(p) => p
None => env
}
for name, binding in scan_env.bindings {
if binding.is_parameter {
excluded.add(name)
}
}
match scan_env.bindings.get("arguments") {
Some(b) => if b.kind == VarBinding { excluded.add("arguments") }
None => ()
}
hoist_eval_annex_b_candidates(_self, stmts, env, top_lex, excluded~)
}
}
///|
/// Hoist let/const declarations with TDZ markers at block scope
fn hoist_block_tdz(
stmts : Array[@ast.Stmt],
env : Environment,
) -> Unit raise Error {
let work : Array[@ast.Stmt] = []
stmts.rev_each(stmt => work.push(stmt))
while work.length() > 0 {
let stmt = work.pop().unwrap()
match stmt {
VarDecl(LetKind, name, _, _) =>
if !env.bindings.contains(name) {
env.def_tdz(name, LetBinding)
}
VarDecl(ConstKind, name, _, _) =>
if !env.bindings.contains(name) {
env.def_tdz(name, ConstBinding)
}
// DestructureDecl with let/const also needs TDZ
DestructureDecl(LetKind, pattern, _, _) =>
hoist_pattern_tdz(pattern, env, LetBinding)
DestructureDecl(ConstKind, pattern, _, _) =>
hoist_pattern_tdz(pattern, env, ConstBinding)
StmtList(inner_stmts, _) =>
inner_stmts.rev_each(inner => work.push(inner))
// Class declarations have TDZ (similar to let)
ClassDecl(name, _, _, _, _) =>
if !env.bindings.contains(name) {
env.def_tdz(name, LetBinding)
}
// Export with declaration: hoist let/const inner declarations with TDZ
ExportNamedDecl(Some(decl), _, _, _, _) => work.push(decl)
// A named default class contributes a module-local lexical binding.
ExportDefaultDecl(expr, _) =>
match (module_default_export_binding(expr), expr) {
(LocalDefaultBinding(name), ClassExpr(_, _, _, _, _)) =>
if !env.bindings.contains(name) {
env.def_tdz(name, LetBinding)
}
_ => ()
}
_ => ()
}
}
}
///|
fn ensure_block_function_binding(
env : Environment,
name : String,
) -> Unit raise Error {
if !env.bindings.contains(name) {
env.def_tdz(name, LetBinding)
}
}
///|
fn initialize_block_function_binding(
env : Environment,
name : String,
value : Value,
) -> Unit raise Error {
match env.bindings.get(name) {
Some(binding) =>
if binding.initialized {
env.assign(name, value)
} else {
env.initialize(name, value)
}
None => abort("unreachable: block function binding was not created")
}
}
///|
/// Instantiate the lexical declarations owned by a Block or CaseBlock.
/// Function declarations are initialized before statement execution and close
/// over the block environment, per BlockDeclarationInstantiation.
fn Interpreter::instantiate_block_declarations(
self : Interpreter,
stmts : Array[@ast.Stmt],
env : Environment,
strict : Bool,
) -> Unit raise Error {
hoist_block_tdz(stmts, env)
for stmt in stmts {
match stmt {
FuncDecl(name, params, body, _, source_text) => {
ensure_block_function_binding(env, name)
validate_function_signature(strict, Some(name), params, body)
let func_val = make_func({
name: Some(name),
params,
body,
closure: env,
strict: is_function_strict(strict, body),
has_name_binding: false,
is_method: false,
source_text,
})
initialize_block_function_binding(env, name, func_val)
}
FuncDeclExt(name, params, rest_param, body, _, source_text) => {
ensure_block_function_binding(env, name)
validate_function_signature_ext(
strict,
Some(name),
params,
rest_param,
body,
)
let func_val = make_func_ext({
name: Some(name),
params,
rest_param,
body,
closure: env,
strict: is_function_strict(strict, body),
has_name_binding: false,
is_method: false,
source_text,
})
initialize_block_function_binding(env, name, func_val)
}
GeneratorDecl(name, params, body, _, source_text) => {
ensure_block_function_binding(env, name)
validate_function_signature(strict, Some(name), params, body)
let func_val = self.make_generator_function(
Some(name),
params,
None,
body,
is_function_strict(strict, body),
env,
source_text~,
)
initialize_block_function_binding(env, name, func_val)
}
GeneratorDeclExt(name, params, rest_param, body, _, source_text) => {
ensure_block_function_binding(env, name)
validate_function_signature_ext(
strict,
Some(name),
params,
rest_param,
body,
)
let func_val = self.make_generator_function_ext(
Some(name),
params,
rest_param,
body,
is_function_strict(strict, body),
env,
source_text~,
)
initialize_block_function_binding(env, name, func_val)
}
AsyncFuncDecl(name, params, body, _, source_text) => {
ensure_block_function_binding(env, name)
validate_function_signature(strict, Some(name), params, body)
let func_val = self.make_async_function(
Some(name),
params,
None,
body,
is_function_strict(strict, body),
env,
source_text~,
)
initialize_block_function_binding(env, name, func_val)
}
AsyncFuncDeclExt(name, params, rest_param, body, _, source_text) => {
ensure_block_function_binding(env, name)
validate_function_signature_ext(
strict,
Some(name),
params,
rest_param,
body,
)
let func_val = self.make_async_function_ext(
Some(name),
params,
rest_param,
body,
is_function_strict(strict, body),
env,
source_text~,
)
initialize_block_function_binding(env, name, func_val)
}
AsyncGeneratorDecl(name, params, body, _, source_text) => {
ensure_block_function_binding(env, name)
validate_function_signature(strict, Some(name), params, body)
let func_val = self.make_async_generator_function(
Some(name),
params,
None,
body,
is_function_strict(strict, body),
env,
source_text~,
)
initialize_block_function_binding(env, name, func_val)
}
AsyncGeneratorDeclExt(name, params, rest_param, body, _, source_text) => {
ensure_block_function_binding(env, name)
validate_function_signature_ext(
strict,
Some(name),
params,
rest_param,
body,
)
let func_val = self.make_async_generator_function_ext(
Some(name),
params,
rest_param,
body,
is_function_strict(strict, body),
env,
source_text~,
)
initialize_block_function_binding(env, name, func_val)
}
_ => ()
}
}
}
///|
/// Hoist pattern bindings with TDZ markers
fn hoist_pattern_tdz(
pattern : @ast.Pattern,
env : Environment,
kind : BindingKind,
) -> Unit raise Error {
for name in @static_semantics.bound_names(pattern) {
if !env.bindings.contains(name) {
env.def_tdz(name, kind)
}
}
}