// ES262 §19.2.1.1 PerformEval steps 8–14 require, before evaluating the eval
// body, that we reject an eval source that contains forbidden constructs in
// the surrounding context:
//
// • SuperCall — outside a derived constructor
// • SuperProperty — outside a method
// • NewTarget — outside a function
// • IdentifierReference — "arguments" inside a class field initializer
//
// All four predicates are computed with the same Contains traversal:
// descend through ordinary expressions and arrow function bodies (arrows are
// transparent to super/this/arguments/new.target), but skip non-arrow
// function bodies, generator/async bodies, and class member bodies entirely
// (per ES262 §15.2.12 — Contains stops at function boundaries, including
// FormalParameters of a skipped function).
//
// We compute all four bits in one pass.
///|
priv struct EvalContainsScan {
mut super_call : Bool
mut super_property : Bool
mut new_target : Bool
mut arguments_ref : Bool
}
///|
priv enum EvalContainsWork {
Stmt(@ast.Stmt)
Expr(@ast.Expr)
Pattern(@ast.Pattern)
}
///|
fn EvalContainsScan::new() -> EvalContainsScan {
{
super_call: false,
super_property: false,
new_target: false,
arguments_ref: false,
}
}
///|
/// Schedule the parts of a class evaluated in the surrounding context:
/// heritage first, then computed member keys in source order. Member bodies,
/// field initializers, and static blocks establish separate Contains regions.
fn schedule_eval_contains_class(
scheduled : Array[EvalContainsWork],
superclass : @ast.Expr?,
class_members : Array[@ast.ClassMember],
) -> Unit {
match superclass {
Some(expr) => scheduled.push(Expr(expr))
None => ()
}
for class_member in class_members {
match class_member {
Method(class_method) =>
if class_method.computed {
scheduled.push(Expr(class_method.key))
}
Field(field) => if field.computed { scheduled.push(Expr(field.key)) }
StaticBlock(_, _) => ()
}
}
}
///|
/// Run the scan over a list of top-level statements (the parsed eval body).
/// Returns a struct with one bit per Contains predicate.
fn scan_eval_contains(stmts : Array[@ast.Stmt]) -> EvalContainsScan {
let scan = EvalContainsScan::new()
let work : Array[EvalContainsWork] = []
for i in (stmts.length() - 1)>=..0 {
work.push(Stmt(stmts[i]))
}
while work.pop() is Some(item) {
let scheduled : Array[EvalContainsWork] = []
match item {
Stmt(stmt) => schedule_eval_contains_stmt(scheduled, stmt)
Expr(expr) => schedule_eval_contains_expr(scan, scheduled, expr)
Pattern(pattern) => schedule_eval_contains_pattern(scheduled, pattern)
}
for i in (scheduled.length() - 1)>=..0 {
work.push(scheduled[i])
}
}
scan
}
///|
fn schedule_eval_contains_stmt(
scheduled : Array[EvalContainsWork],
stmt : @ast.Stmt,
) -> Unit {
match stmt {
ExprStmt(e, _) => scheduled.push(Expr(e))
VarDecl(_, _, init, _) =>
match init {
Some(e) => scheduled.push(Expr(e))
None => ()
}
Block(stmts, _) | StmtList(stmts, _) =>
for s in stmts {
scheduled.push(Stmt(s))
}
IfStmt(cond, then_s, else_s, _) => {
scheduled.push(Expr(cond))
scheduled.push(Stmt(then_s))
match else_s {
Some(s) => scheduled.push(Stmt(s))
None => ()
}
}
WhileStmt(cond, body, _) => {
scheduled.push(Expr(cond))
scheduled.push(Stmt(body))
}
DoWhileStmt(body, cond, _) => {
scheduled.push(Stmt(body))
scheduled.push(Expr(cond))
}
ForStmt(init, cond, update, body, _) => {
match init {
Some(s) => scheduled.push(Stmt(s))
None => ()
}
match cond {
Some(e) => scheduled.push(Expr(e))
None => ()
}
match update {
Some(e) => scheduled.push(Expr(e))
None => ()
}
scheduled.push(Stmt(body))
}
ForInStmt(_, _, src, body, _)
| ForOfStmt(_, _, src, body, _)
| AsyncForOfStmt(_, _, src, body, _) => {
scheduled.push(Expr(src))
scheduled.push(Stmt(body))
}
ForInStmtPat(_, pattern, src, body, _)
| ForOfStmtPat(_, pattern, src, body, _)
| AsyncForOfStmtPat(_, pattern, src, body, _) => {
scheduled.push(Pattern(pattern))
scheduled.push(Expr(src))
scheduled.push(Stmt(body))
}
ForInExpr(target, src, body, _)
| ForOfExpr(target, src, body, _)
| AsyncForOfExpr(target, src, body, _) => {
scheduled.push(Expr(target))
scheduled.push(Expr(src))
scheduled.push(Stmt(body))
}
ReturnStmt(e, _) =>
match e {
Some(e) => scheduled.push(Expr(e))
None => ()
}
ThrowStmt(e, _) => scheduled.push(Expr(e))
TryCatchStmt(try_body, catch_param, catch_body, finally_body, _) => {
for s in try_body {
scheduled.push(Stmt(s))
}
match catch_param {
Some(pattern) => scheduled.push(Pattern(pattern))
None => ()
}
match catch_body {
Some(stmts) =>
for s in stmts {
scheduled.push(Stmt(s))
}
None => ()
}
match finally_body {
Some(stmts) =>
for s in stmts {
scheduled.push(Stmt(s))
}
None => ()
}
}
SwitchStmt(disc, cases, _) => {
scheduled.push(Expr(disc))
for c in cases {
match c.condition {
Some(e) => scheduled.push(Expr(e))
None => ()
}
for s in c.body {
scheduled.push(Stmt(s))
}
}
}
DestructureDecl(_, pattern, init, _) => {
scheduled.push(Pattern(pattern))
scheduled.push(Expr(init))
}
LabeledStmt(_, body, _) => scheduled.push(Stmt(body))
WithStmt(obj, body, _) => {
scheduled.push(Expr(obj))
scheduled.push(Stmt(body))
}
ClassDecl(_, superclass, members, _, _) =>
schedule_eval_contains_class(scheduled, superclass, members)
// Skip-entirely productions: each introduces its own function-scope
// binding for new.target/arguments/super, so Contains stops here.
FuncDecl(_, _, _, _, _)
| FuncDeclExt(_, _, _, _, _, _)
| GeneratorDecl(_, _, _, _, _)
| GeneratorDeclExt(_, _, _, _, _, _)
| AsyncFuncDecl(_, _, _, _, _)
| AsyncFuncDeclExt(_, _, _, _, _, _)
| AsyncGeneratorDecl(_, _, _, _, _)
| AsyncGeneratorDeclExt(_, _, _, _, _, _) => ()
// Module-level decls: import/export shouldn't appear in eval source, but
// be defensive — they have nothing scan-relevant in their headers.
_ => ()
}
}
///|
/// Scan a destructuring pattern. Patterns themselves bind names (irrelevant
/// to Contains), but they may carry default expressions and computed keys,
/// both of which evaluate in the surrounding context and therefore count
/// for the four predicates.
fn schedule_eval_contains_pattern(
scheduled : Array[EvalContainsWork],
pat : @ast.Pattern,
) -> Unit {
match pat {
IdentPat(_) => ()
ArrayPat(elements, rest) => {
for el in elements {
match el {
Some(p) => scheduled.push(Pattern(p))
None => ()
}
}
match rest {
Some(p) => scheduled.push(Pattern(p))
None => ()
}
}
ObjectPat(props, rest) => {
for prop in props {
match prop.computed_key {
Some(e) => scheduled.push(Expr(e))
None => ()
}
scheduled.push(Pattern(prop.value))
match prop.default_val {
Some(e) => scheduled.push(Expr(e))
None => ()
}
}
match rest {
Some(p) => scheduled.push(Pattern(p))
None => ()
}
}
DefaultPat(inner, expr) => {
scheduled.push(Pattern(inner))
scheduled.push(Expr(expr))
}
AssignTarget(expr) => scheduled.push(Expr(expr))
}
}
///|
fn schedule_eval_contains_expr(
scan : EvalContainsScan,
scheduled : Array[EvalContainsWork],
expr : @ast.Expr,
) -> Unit {
match expr {
// Leaves
SuperCall(args, _) => {
scan.super_call = true
for a in args {
scheduled.push(Expr(a))
}
}
SuperMember(_, _) => scan.super_property = true
SuperComputedMember(key, _) => {
scan.super_property = true
scheduled.push(Expr(key))
}
SuperMemberAssign(_, rhs, _) => {
scan.super_property = true
scheduled.push(Expr(rhs))
}
SuperComputedAssign(key, rhs, _) => {
scan.super_property = true
scheduled.push(Expr(key))
scheduled.push(Expr(rhs))
}
NewTargetExpr(_) => scan.new_target = true
Ident("arguments", _) => scan.arguments_ref = true
PrivateIdent(_, _) => ()
// Skip-entirely: non-arrow function/generator/async bodies define their
// own super/arguments/new.target context per ES262 §15.2.12.
FuncExpr(_, _, _, _, _)
| FuncExprExt(_, _, _, _, _, _)
| GeneratorExpr(_, _, _, _, _)
| GeneratorExprExt(_, _, _, _, _, _)
| AsyncFuncExpr(_, _, _, _, _)
| AsyncFuncExprExt(_, _, _, _, _, _)
| AsyncGeneratorExpr(_, _, _, _, _)
| AsyncGeneratorExprExt(_, _, _, _, _, _) => ()
ClassExpr(_, superclass, members, _, _) =>
schedule_eval_contains_class(scheduled, superclass, members)
// Arrows: transparent for the four predicates per spec — and that
// transparency extends to the parameter list. Non-arrow function
// boundaries skip params (they're the function's own concern), but
// arrow params/defaults run in the surrounding scope (Codex review on
// PR #100 caught this gap, V8-confirmed). ArrowFunc has `Array[String]`
// params — no expressions to scan. ArrowFuncExt carries Param values
// with optional default_val and destructuring pattern, both of which
// must be visited.
ArrowFunc(_, body, _, _) | AsyncArrowFunc(_, body, _, _) =>
for s in body {
scheduled.push(Stmt(s))
}
ArrowFuncExt(params, _, body, _, _)
| AsyncArrowFuncExt(params, _, body, _, _) => {
for p in params {
match p.default_val {
Some(e) => scheduled.push(Expr(e))
None => ()
}
match p.pattern {
Some(pat) => scheduled.push(Pattern(pat))
None => ()
}
}
for s in body {
scheduled.push(Stmt(s))
}
}
// Recurse-into: every other expression descends through children.
Binary(_, l, r, _) | Comma(l, r, _) => {
scheduled.push(Expr(l))
scheduled.push(Expr(r))
}
Unary(_, e, _)
| Grouping(e, _)
| SpreadExpr(e, _)
| YieldExpr(Some(e), _, _)
| AwaitExpr(e, _) => scheduled.push(Expr(e))
YieldExpr(None, _, _) => ()
Assign(_, e, _) => scheduled.push(Expr(e))
Call(callee, args, _)
| OptionalCall(callee, args, _)
| NewExpr(callee, args, _) => {
scheduled.push(Expr(callee))
for a in args {
scheduled.push(Expr(a))
}
}
Member(obj, _, _)
| OptionalMember(obj, _, _)
| ChainMember(obj, _, _)
| PrivateMember(obj, _, _) => scheduled.push(Expr(obj))
ComputedMember(obj, key, _)
| OptionalComputedMember(obj, key, _)
| ChainComputedMember(obj, key, _) => {
scheduled.push(Expr(obj))
scheduled.push(Expr(key))
}
Ternary(c, t, f, _) => {
scheduled.push(Expr(c))
scheduled.push(Expr(t))
scheduled.push(Expr(f))
}
MemberAssign(obj, _, rhs, _) => {
scheduled.push(Expr(obj))
scheduled.push(Expr(rhs))
}
PrivateMemberAssign(obj, _, rhs, _) => {
scheduled.push(Expr(obj))
scheduled.push(Expr(rhs))
}
ComputedAssign(obj, key, rhs, _) => {
scheduled.push(Expr(obj))
scheduled.push(Expr(key))
scheduled.push(Expr(rhs))
}
UpdateExpr(_, target, _, _) => scheduled.push(Expr(target))
CompoundAssign(_, lhs, rhs, _) => {
scheduled.push(Expr(lhs))
scheduled.push(Expr(rhs))
}
WebCompatCallAssign(call_expr, rhs, _) => {
scheduled.push(Expr(call_expr))
scheduled.push(Expr(rhs))
}
// Property keys: prop.key is StringLit for non-computed keys (per
// ast/ast.mbt:83 comment), so descending into it is safe — a literal
// `arguments` written as `{ arguments: 1 }` is a StringLit, not an
// Ident, and won't trigger arguments_ref.
ObjectLit(props, _) =>
for prop in props {
scheduled.push(Expr(prop.key))
scheduled.push(Expr(prop.value))
}
ArrayLit(elements, _) =>
for e in elements {
scheduled.push(Expr(e))
}
TemplateLit(_, exprs, _) =>
for e in exprs {
scheduled.push(Expr(e))
}
TaggedTemplate(tag, _, exprs, _) => {
scheduled.push(Expr(tag))
for e in exprs {
scheduled.push(Expr(e))
}
}
DestructureAssign(pattern, rhs, _) => {
scheduled.push(Pattern(pattern))
scheduled.push(Expr(rhs))
}
// Leaves with nothing to scan. (Ident("arguments") is handled above.)
NumberLit(_, _, _)
| StringLit(_, _, _, _)
| BoolLit(_, _)
| NullLit(_)
| UndefinedLit(_)
| ArrayHole(_)
| Ident(_, _)
| ThisExpr(_)
| RegexLit(_, _, _) => ()
}
}