///|
/// Resolve the target of a `super.x` / `super[k]` access (ES2022 §13.3.7.1
/// MakeSuperPropertyReference).
///
/// Per spec, super resolves via the method's [[HomeObject]] at *call* time by
/// invoking its [[GetPrototypeOf]] internal method. The Proxy arm preserves a
/// possible getPrototypeOf trap before the later property [[Get]]/[[Set]].
/// [[SuperPrototype]] remains only as a fallback for legacy constructor-call
/// environments that do not yet carry a [[HomeObject]].
fn Interpreter::resolve_super_target(
self : Interpreter,
env : Environment,
) -> Value raise Error {
let home = env.get("[[HomeObject]]") catch { _ => Undefined }
match home {
Object(_) | Array(_) | Proxy(_) =>
return object_get_prototype_of(self, home)
_ => ()
}
env.get("[[SuperPrototype]]") catch {
_ => Undefined
}
}
///|
fn Interpreter::require_super_target(
self : Interpreter,
env : Environment,
access : String,
loc : @token.Loc,
) -> Value raise Error {
match self.resolve_super_target(env) {
Undefined =>
raise @errors.ReferenceError(
message=access +
" used but no [[SuperPrototype]] in scope at line \{loc.line}",
)
Null =>
raise @errors.TypeError(
message=format_loc_context("Cannot convert null to object", loc),
)
target => target
}
}
///|
priv enum EvalExprYieldWork {
Expr(@ast.Expr)
Pattern(@ast.Pattern)
}
///|
fn eval_expr_work_may_contain_yield(work : Array[EvalExprYieldWork]) -> Bool {
while work.pop() is Some(item) {
let scheduled : Array[EvalExprYieldWork] = []
match item {
Expr(YieldExpr(_, _, _)) => return true
Expr(ClassExpr(_, superclass, members, _, _)) => {
match superclass {
Some(expr) => scheduled.push(Expr(expr))
None => ()
}
for class_member in members {
match class_member {
@ast.ClassMember::Method(class_method) => {
scheduled.push(Expr(class_method.key))
scheduled.push(Expr(class_method.value))
}
@ast.ClassMember::Field(field) => {
scheduled.push(Expr(field.key))
match field.initializer {
Some(initializer) => scheduled.push(Expr(initializer))
None => ()
}
}
@ast.ClassMember::StaticBlock(_) => ()
}
}
}
Expr(DestructureAssign(pattern, expr, _)) => {
scheduled.push(Pattern(pattern))
scheduled.push(Expr(expr))
}
Expr(expr) =>
ignore(
@ast.expr_immediate_children_any(expr, child => {
scheduled.push(Expr(child))
false
}),
)
Pattern(pattern) =>
ignore(
@ast.pattern_immediate_children_any(
pattern,
child => {
scheduled.push(Pattern(child))
false
},
child => {
scheduled.push(Expr(child))
false
},
),
)
}
for i in (scheduled.length() - 1)>=..0 {
work.push(scheduled[i])
}
}
false
}
///|
fn expr_may_contain_yield(expr : @ast.Expr) -> Bool {
eval_expr_work_may_contain_yield([Expr(expr)])
}
///|
fn Interpreter::get_symbol_property_from_prototype(
self : Interpreter,
receiver : Value,
proto : Value,
sym : SymbolData,
loc : @token.Loc,
) -> Value raise Error {
self.get_property_key_with_receiver(proto, Symbol(sym), receiver, loc)
}
///|
fn Interpreter::get_computed_property_from_prototype(
self : Interpreter,
receiver : Value,
proto : Value,
key : Value,
loc : @token.Loc,
) -> Value raise Error {
self.get_property_key_with_receiver(proto, key, receiver, loc)
}
///|
pub fn Interpreter::eval_super_property(
self : Interpreter,
env : Environment,
prop : String,
loc : @token.Loc,
) -> Value raise Error {
let this_val = eval_this_value(env)
let super_proto = self.resolve_super_target(env)
match super_proto {
Undefined =>
raise @errors.ReferenceError(
message="super.prop used but no [[SuperPrototype]] in scope at line \{loc.line}",
)
Null =>
raise @errors.TypeError(
message="Cannot read property from null super base at line \{loc.line}",
)
_ => self.get_property_from_prototype(this_val, super_proto, prop, loc)
}
}
///|
pub fn Interpreter::eval_super_computed_property(
self : Interpreter,
env : Environment,
key : Value,
loc : @token.Loc,
) -> Value raise Error {
let this_val = eval_this_value(env)
let super_proto = self.resolve_super_target(env)
match super_proto {
Undefined =>
raise @errors.ReferenceError(
message="super[expr] used but no [[SuperPrototype]] in scope at line \{loc.line}",
)
Null =>
raise @errors.TypeError(
message="Cannot read property from null super base at line \{loc.line}",
)
_ =>
self.get_computed_property_from_prototype(this_val, super_proto, key, loc)
}
}
///|
pub fn Interpreter::eval_super_property_call_reference(
self : Interpreter,
env : Environment,
prop : String,
loc : @token.Loc,
) -> (Value, Value) raise Error {
let this_val = eval_this_value(env)
let super_proto = self.resolve_super_target(env)
match super_proto {
Undefined =>
raise @errors.ReferenceError(
message="super.prop used but no [[SuperPrototype]] in scope at line \{loc.line}",
)
Null =>
raise @errors.TypeError(
message="Cannot read property from null super base at line \{loc.line}",
)
_ => {
let func_val = self.get_property_from_prototype(
this_val, super_proto, prop, loc,
)
(this_val, func_val)
}
}
}
///|
pub fn Interpreter::eval_super_computed_call_reference(
self : Interpreter,
env : Environment,
key : Value,
loc : @token.Loc,
) -> (Value, Value) raise Error {
let this_val = eval_this_value(env)
let super_proto = self.resolve_super_target(env)
match super_proto {
Undefined =>
raise @errors.ReferenceError(
message="super[expr] used but no [[SuperPrototype]] in scope at line \{loc.line}",
)
Null =>
raise @errors.TypeError(
message="Cannot read property from null super base at line \{loc.line}",
)
_ => {
let func_val = self.get_computed_property_from_prototype(
this_val, super_proto, key, loc,
)
(this_val, func_val)
}
}
}
///|
/// Resolve `this` with the same TDZ-to-derived-constructor error mapping used
/// by expression evaluation. Compiled execution uses this helper instead of
/// treating `this` as an ordinary identifier.
pub fn eval_this_value(env : Environment) -> Value raise Error {
env.get("this") catch {
@errors.ReferenceError(message~) => {
if message.contains("before initialization") {
raise @errors.ReferenceError(
message="Must call super constructor in derived class before accessing 'this' or returning from derived constructor",
)
}
Value::Undefined
}
other => raise other
}
}
///|
/// Resolve `new.target` from the current function environment. Scripts and
/// ordinary calls without a constructor binding match the tree-walker fallback
/// to `undefined`.
pub fn eval_new_target_value(env : Environment) -> Value {
env.get("") catch {
_ => Undefined
}
}
///|
/// Evaluate an object-literal property's value. For method-shorthand
/// props the returned function must not carry the §15.2.5 self-name
/// binding (methods have no FunctionExpressionName), so we strip it
/// off the resulting FuncData. For `key: value` props the value passes
/// through — a named FunctionExpression there still gets its self-name.
fn Interpreter::eval_prop_value(
self : Interpreter,
ctx : ExecContext,
prop : @ast.Property,
method_env : Environment,
) -> Value raise Error {
if prop.is_method {
match prop.value {
FuncExpr(name, params, body, _, source_text) => {
validate_function_signature(ctx.strict, None, params, body)
let func_data : FuncData = {
name,
params,
body,
closure: method_env,
strict: is_function_strict(ctx.strict, body),
has_name_binding: false,
is_method: true,
source_text,
}
return make_func(func_data)
}
FuncExprExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
None,
params,
rest_param,
body,
)
let func_data : FuncDataExt = {
name,
params,
rest_param,
body,
closure: method_env,
strict: is_function_strict(ctx.strict, body),
has_name_binding: false,
is_method: true,
source_text,
}
return make_func_ext(func_data)
}
// Generator/async methods: fast path with has_name_binding=false.
// strip_self_name_binding is a no-op for InterpreterCallable, so we
// must suppress the binding at creation time (§15.2.5 does not apply
// to MethodDefinitionEvaluation).
GeneratorExpr(name, params, body, _, source_text) => {
validate_function_signature(ctx.strict, None, params, body)
let strict = is_function_strict(ctx.strict, body)
return self.make_generator_function(
name,
params,
None,
body,
strict,
method_env,
has_name_binding=false,
is_method=true,
source_text~,
)
}
GeneratorExprExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
None,
params,
rest_param,
body,
)
let strict = is_function_strict(ctx.strict, body)
return self.make_generator_function_ext(
name,
params,
rest_param,
body,
strict,
method_env,
has_name_binding=false,
is_method=true,
source_text~,
)
}
AsyncFuncExpr(name, params, body, _, source_text) => {
validate_function_signature(ctx.strict, None, params, body)
let strict = is_function_strict(ctx.strict, body)
return self.make_async_function(
name,
params,
None,
body,
strict,
method_env,
has_name_binding=false,
is_method=true,
source_text~,
)
}
AsyncFuncExprExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
None,
params,
rest_param,
body,
)
let strict = is_function_strict(ctx.strict, body)
return self.make_async_function_ext(
name,
params,
rest_param,
body,
strict,
method_env,
has_name_binding=false,
is_method=true,
source_text~,
)
}
AsyncGeneratorExpr(name, params, body, _, source_text) => {
validate_function_signature(ctx.strict, None, params, body)
let strict = is_function_strict(ctx.strict, body)
return self.make_async_generator_function(
name,
params,
None,
body,
strict,
method_env,
has_name_binding=false,
is_method=true,
source_text~,
)
}
AsyncGeneratorExprExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
None,
params,
rest_param,
body,
)
let strict = is_function_strict(ctx.strict, body)
return self.make_async_generator_function_ext(
name,
params,
rest_param,
body,
strict,
method_env,
has_name_binding=false,
is_method=true,
source_text~,
)
}
_ => ()
}
}
let v = self.eval_expr(ctx, prop.value, method_env)
if prop.is_method {
// Strip §15.2.5 self-name binding (methods have no FunctionExpressionName),
// then mark as method-shorthand so `new obj.m()` throws TypeError per
// ES §15.4.5 MethodDefinitionEvaluation + §13.3.5.1 step 5.
mark_as_method(strip_self_name_binding(v))
} else {
v
}
}
///|
fn object_literal_inferred_name(key : Value) -> String raise Error {
match key {
Symbol(sym) =>
match sym.description {
Some(description) => "[\{description}]"
None => ""
}
_ => to_js_string(key)
}
}
///|
fn Interpreter::eval_named_prop_value(
self : Interpreter,
ctx : ExecContext,
prop : @ast.Property,
method_env : Environment,
key : Value,
) -> Value raise Error {
if prop.is_method {
self.eval_prop_value(ctx, prop, method_env)
} else {
self.eval_named_expr(
ctx,
prop.value,
method_env,
object_literal_inferred_name(key),
)
}
}
///|
pub fn apply_object_literal_proto_property(
target : Value,
value : Value,
) -> Unit raise Error {
match target {
Object(data) =>
match value {
Object(_) | Array(_) | Map(_) | Set(_) => data.prototype = value
Null => data.prototype = Null
_ => data.bag.properties["__proto__"] = value
}
_ =>
raise @errors.InternalError(
message="object literal __proto__ target was not an object",
)
}
}
///|
pub fn Interpreter::to_object_literal_property_key(
self : Interpreter,
key : Value,
) -> Value raise Error {
to_property_key(key, interp=Some(self))
}
///|
fn frozen_template_data_descriptor(enumerable : Bool) -> PropDescriptor {
{
writable: false,
enumerable,
configurable: false,
getter: None,
setter: None,
is_accessor: false,
}
}
///|
fn freeze_template_array(value : Value) -> Unit {
match value {
Array(data) => {
data.extensible = false
data.length_writable = false
for i = 0; i < data.elements.length(); i = i + 1 {
data.bag.descriptors[i.to_string()] = frozen_template_data_descriptor(
true,
)
}
}
_ => ()
}
}
///|
pub fn make_tagged_template_object(quasis : Array[(String, String?)]) -> Value {
let raw_vals : Array[Value] = []
let cooked_vals : Array[Value] = []
for q in quasis {
raw_vals.push(String_(q.0))
cooked_vals.push(
match q.1 {
Some(s) => String_(s)
None => Undefined
},
)
}
let raw_arr = make_array(raw_vals)
freeze_template_array(raw_arr)
let template_obj = make_array(cooked_vals)
match template_obj {
Array(data) => {
set_array_named_prop(data, "raw", raw_arr)
data.bag.descriptors["raw"] = frozen_template_data_descriptor(false)
}
_ => ()
}
freeze_template_array(template_obj)
template_obj
}
///|
fn tagged_template_cache_key(
loc : @token.Loc,
quasis : Array[(String, String?)],
) -> String {
let buf = StringBuilder::new()
buf.write_string(loc.line.to_string())
buf.write_string(":")
buf.write_string(loc.col.to_string())
buf.write_string(":")
buf.write_string(loc.offset.to_string())
for q in quasis {
buf.write_string(":")
buf.write_string(q.0.length().to_string())
buf.write_string(":")
buf.write_string(q.0)
}
buf.to_string()
}
///|
// Per-interpreter template-object cache. It is stored as an internal global
// binding so it follows the Interpreter/global lifetime, and all access stays
// behind Interpreter methods rather than a module-level mutable table.
const TEMPLATE_OBJECT_CACHE_BINDING = "[[TemplateObjectCache]]"
///|
fn Interpreter::get_template_object_cache(self : Interpreter) -> ObjectData {
match self.global.bindings.get(TEMPLATE_OBJECT_CACHE_BINDING) {
Some(binding) =>
match binding.value {
Object(data) => return data
_ => ()
}
None => ()
}
let cache_data : ObjectData = {
bag: PropertyBag(),
prototype: Null,
callable: None,
class_name: "Object",
extensible: true,
arraybuffer_state: None,
}
self.global.bindings[TEMPLATE_OBJECT_CACHE_BINDING] = {
value: Object(cache_data),
kind: VarBinding,
initialized: true,
annex_b_hoisted: false,
is_parameter: false,
}
cache_data
}
///|
pub fn Interpreter::get_cached_tagged_template_object(
self : Interpreter,
key : String,
quasis : Array[(String, String?)],
) -> Value {
let cache = self.get_template_object_cache()
match cache.bag.properties.get(key) {
Some(value) => value
None => {
let value = make_tagged_template_object(quasis)
cache.bag.properties[key] = value
value
}
}
}
///|
fn Interpreter::get_tagged_template_object(
self : Interpreter,
loc : @token.Loc,
quasis : Array[(String, String?)],
) -> Value {
self.get_cached_tagged_template_object(
tagged_template_cache_key(loc, quasis),
quasis,
)
}
///|
pub fn apply_object_literal_data_property(
target : Value,
key : Value,
value : Value,
) -> Unit raise Error {
match target {
Object(data) =>
match key {
Symbol(sym) => {
let sym_name = match sym.description {
Some(d) => "[\{d}]"
None => ""
}
set_function_name(value, sym_name)
data.bag.symbol_properties[sym.id] = value
}
_ => {
let key_str = to_js_string(key)
set_function_name(value, key_str)
data.bag.properties[key_str] = value
}
}
_ =>
raise @errors.InternalError(
message="object literal data property target was not an object",
)
}
}
///|
/// Set a static (compile-time string keyed) data property on an object literal
/// accumulator. Unlike `apply_object_literal_data_property`, this performs no
/// function-name inference: the bytecode lowering emits a separate
/// `SetFunctionName` instruction for static keys, so naming is already handled
/// upstream and must not be re-applied here.
pub fn apply_object_literal_static_data_property(
target : Value,
key : String,
value : Value,
) -> Unit raise Error {
match target {
Object(data) => data.bag.properties[key] = value
_ =>
raise @errors.InternalError(
message="object literal static data property target was not an object",
)
}
}
///|
/// Append one evaluated element to an array literal accumulator.
pub fn apply_array_literal_element(
target : Value,
value : Value,
) -> Unit raise Error {
match target {
Array(data) => data.elements.push(value)
_ =>
raise @errors.InternalError(
message="array literal element target was not an array",
)
}
}
///|
/// Append already-spread iterable values to an array literal accumulator. The
/// caller is responsible for running the iterator protocol (interpreter
/// semantics); this operation only lands the resulting values into storage.
pub fn apply_array_literal_spread(
target : Value,
values : Array[Value],
) -> Unit raise Error {
match target {
Array(data) =>
for value in values {
data.elements.push(value)
}
_ =>
raise @errors.InternalError(
message="array literal spread target was not an array",
)
}
}
///|
/// Append a single elision (hole) to an array literal accumulator: the index is
/// recorded as a hole and the slot is filled with `undefined` so that `length`
/// advances while `in`/iteration treat the index as absent.
pub fn apply_array_literal_hole(target : Value) -> Unit raise Error {
match target {
Array(data) => {
data.holes[data.elements.length()] = ()
data.elements.push(Value::Undefined)
}
_ =>
raise @errors.InternalError(
message="array literal hole target was not an array",
)
}
}
///|
fn force_object_literal_accessor_name(accessor : Value, name : String) -> Unit {
match accessor {
Object(data) if data.callable is Some(_) =>
data.bag.properties["name"] = String_(name)
_ => ()
}
}
///|
pub fn apply_object_literal_accessor_property(
target : Value,
key : Value,
accessor : Value,
is_getter : Bool,
) -> Unit raise Error {
match target {
Object(data) =>
match key {
Symbol(sym) => {
let accessor_name = match (is_getter, sym.description) {
(true, Some(d)) => "get [\{d}]"
(true, None) => "get "
(false, Some(d)) => "set [\{d}]"
(false, None) => "set "
}
force_object_literal_accessor_name(accessor, accessor_name)
if !data.bag.symbol_properties.contains(sym.id) {
data.bag.symbol_properties[sym.id] = Undefined
}
let existing = data.bag.symbol_descriptors.get(sym.id)
data.bag.symbol_descriptors[sym.id] = {
writable: false,
enumerable: true,
configurable: true,
getter: if is_getter {
Some(accessor)
} else {
match existing {
Some(desc) => desc.getter
None => None
}
},
setter: if is_getter {
match existing {
Some(desc) => desc.setter
None => None
}
} else {
Some(accessor)
},
is_accessor: true,
}
}
_ => {
let key_str = to_js_string(key)
let accessor_name = if is_getter {
"get " + key_str
} else {
"set " + key_str
}
force_object_literal_accessor_name(accessor, accessor_name)
if !data.bag.properties.contains(key_str) {
data.bag.properties[key_str] = Undefined
}
let existing = data.bag.descriptors.get(key_str)
data.bag.descriptors[key_str] = {
writable: false,
enumerable: true,
configurable: true,
getter: if is_getter {
Some(accessor)
} else {
match existing {
Some(desc) => desc.getter
None => None
}
},
setter: if is_getter {
match existing {
Some(desc) => desc.setter
None => None
}
} else {
Some(accessor)
},
is_accessor: true,
}
}
}
_ =>
raise @errors.InternalError(
message="object literal accessor property target was not an object",
)
}
}
///|
pub fn Interpreter::copy_object_spread_properties(
self : Interpreter,
target : Value,
source : Value,
loc : @token.Loc,
) -> Unit raise Error {
match target {
Object(_) => ()
_ =>
raise @errors.InternalError(
message="object spread target was not an object",
)
}
// CopyDataProperties: [[OwnPropertyKeys]] → [[GetOwnProperty]] → [[Get]].
// The target is a fresh ordinary object, so building its owned maps locally
// is the unobservable CreateDataProperty shell around those dispatchers.
if source is (Null | Undefined) {
return
}
for key in self.own_property_keys(source) {
match self.get_own_property(source, key) {
Some((desc, _)) if desc.enumerable => {
let value = self.get_computed_property(source, key, loc)
let succeeded = self.define_own_property(
target,
key,
PartialDescriptor::data_default(value),
loc,
)
if !succeeded {
raise @errors.TypeError(
message=format_loc_context("Cannot create data property", loc),
)
}
}
_ => ()
}
}
}
///|
/// If func_val is null/undefined, short-circuit to (Undefined, true).
/// Otherwise evaluate arguments and call with the given receiver.
/// Shared tail for all OptionalCall arms.
fn Interpreter::eval_optional_call_tail(
self : Interpreter,
ctx : ExecContext,
func_val : Value,
receiver : Value,
arg_exprs : Array[@ast.Expr],
env : Environment,
loc : @token.Loc,
) -> (Value, Bool) raise Error {
match func_val {
Null | Undefined => (Undefined, true)
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
(self.call_value(func_val, receiver, args, loc), false)
}
}
}
///|
fn Interpreter::eval_chain_expr(
self : Interpreter,
ctx : ExecContext,
expr : @ast.Expr,
env : Environment,
) -> (Value, Bool) raise Error {
match expr {
OptionalMember(obj_expr, prop, loc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => (Undefined, true)
_ => (self.get_property(obj, prop, loc), false)
}
}
OptionalComputedMember(obj_expr, key_expr, loc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => (Undefined, true)
_ => {
let key = self.eval_expr(ctx, key_expr, env)
(self.get_computed_property(obj, key, loc), false)
}
}
}
ChainMember(obj_expr, prop, loc) => {
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
(Undefined, true)
} else {
(self.get_property(obj, prop, loc), false)
}
}
ChainComputedMember(obj_expr, key_expr, loc) => {
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
(Undefined, true)
} else {
let key = self.eval_expr(ctx, key_expr, env)
(self.get_computed_property(obj, key, loc), false)
}
}
OptionalCall(callee_expr, arg_exprs, loc) => {
// Peel grouping parentheses to recover the reference/receiver (§13.2.9).
// Grouping preserves the Reference, so (a.b)?.() should call with this=a.
let unwrapped = unwrap_grouping(callee_expr)
match unwrapped {
OptionalMember(obj_expr, prop, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => (Undefined, true)
_ => {
let func_val = self.get_property(obj, prop, mloc)
self.eval_optional_call_tail(
ctx, func_val, obj, arg_exprs, env, loc,
)
}
}
}
ChainMember(obj_expr, prop, mloc) => {
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
(Undefined, true)
} else {
let func_val = self.get_property(obj, prop, mloc)
self.eval_optional_call_tail(
ctx, func_val, obj, arg_exprs, env, loc,
)
}
}
OptionalComputedMember(obj_expr, key_expr, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => (Undefined, true)
_ => {
let key = self.eval_expr(ctx, key_expr, env)
let func_val = self.get_computed_property(obj, key, mloc)
self.eval_optional_call_tail(
ctx, func_val, obj, arg_exprs, env, loc,
)
}
}
}
ChainComputedMember(obj_expr, key_expr, mloc) => {
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
(Undefined, true)
} else {
let key = self.eval_expr(ctx, key_expr, env)
let func_val = self.get_computed_property(obj, key, mloc)
self.eval_optional_call_tail(
ctx, func_val, obj, arg_exprs, env, loc,
)
}
}
Member(obj_expr, prop, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let func_val = self.get_property(obj, prop, mloc)
self.eval_optional_call_tail(ctx, func_val, obj, arg_exprs, env, loc)
}
ComputedMember(obj_expr, key_expr, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let key = self.eval_expr(ctx, key_expr, env)
let func_val = self.get_computed_property(obj, key, mloc)
self.eval_optional_call_tail(ctx, func_val, obj, arg_exprs, env, loc)
}
PrivateMember(obj_expr, name, _) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let func_val = get_private_member(obj, name, env)
self.eval_optional_call_tail(ctx, func_val, obj, arg_exprs, env, loc)
}
SuperMember(prop, sloc) => {
let (this_val, func_val) = self.eval_super_property_call_reference(
env, prop, sloc,
)
self.eval_optional_call_tail(
ctx, func_val, this_val, arg_exprs, env, loc,
)
}
SuperComputedMember(key_expr, sloc) => {
let _ = eval_this_value(env)
let key = self.eval_expr(ctx, key_expr, env)
let (this_val, func_val) = self.eval_super_computed_call_reference(
env, key, sloc,
)
self.eval_optional_call_tail(
ctx, func_val, this_val, arg_exprs, env, loc,
)
}
_ => {
let callee = self.eval_expr(ctx, callee_expr, env)
match callee {
Null | Undefined => (Undefined, true)
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
(self.call_value(callee, Undefined, args, loc), false)
}
}
}
}
}
// Call(OptionalCall(...), ...): continuation call after an optional call.
// If the inner OptionalCall short-circuits, propagate short-circuit.
// Otherwise the inner call produced a value; call it with no receiver.
// Must recurse on the inner OptionalCall, not on `expr` (which is the
// outer Call), to avoid infinite recursion.
Call(
OptionalCall(inner_callee, inner_args, inner_loc),
continuation_args,
loc
) => {
let (value, short_circuited) = self.eval_chain_expr(
ctx,
OptionalCall(inner_callee, inner_args, inner_loc),
env,
)
if short_circuited {
(Undefined, true)
} else {
let args = self.eval_args_with_spread(ctx, continuation_args, env)
(self.call_value(value, Undefined, args, loc), false)
}
}
Call(OptionalMember(obj_expr, prop, mloc), arg_exprs, loc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => (Undefined, true)
_ => {
let func_val = self.get_property(obj, prop, mloc)
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
(self.call_value(func_val, obj, args, loc), false)
}
}
}
Call(ChainMember(obj_expr, prop, mloc), arg_exprs, loc) => {
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
(Undefined, true)
} else {
let func_val = self.get_property(obj, prop, mloc)
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
(self.call_value(func_val, obj, args, loc), false)
}
}
Call(OptionalComputedMember(obj_expr, key_expr, mloc), arg_exprs, loc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => (Undefined, true)
_ => {
let key = self.eval_expr(ctx, key_expr, env)
let func_val = self.get_computed_property(obj, key, mloc)
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
(self.call_value(func_val, obj, args, loc), false)
}
}
}
Call(ChainComputedMember(obj_expr, key_expr, mloc), arg_exprs, loc) => {
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
(Undefined, true)
} else {
let key = self.eval_expr(ctx, key_expr, env)
let func_val = self.get_computed_property(obj, key, mloc)
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
(self.call_value(func_val, obj, args, loc), false)
}
}
_ => (self.eval_expr(ctx, expr, env), false)
}
}
///|
///|
/// Resolve the [[PrivateBrand]] from an environment.
/// Raises ReferenceError if the brand is not found (expression outside a class context).
fn resolve_private_brand(env : Environment, name : String) -> Value raise Error {
env.get("[[PrivateBrand]]") catch {
_ =>
raise @errors.ReferenceError(
message="Private field '#\{name}' must be declared in an enclosing class",
)
}
}
///|
fn get_private_member(
obj : Value,
name : String,
env : Environment,
) -> Value raise Error {
let brand = resolve_private_brand(env, name)
// Check private methods first (shared per-class, not instance-specific)
let methods_obj = env.get("[[PrivateMethods]]") catch { _ => Undefined }
match methods_obj {
Object(mobj_data) =>
match mobj_data.bag.properties.get(name) {
Some(method_val) => {
if !has_brand(obj, brand) {
raise @errors.TypeError(
message="Cannot read private member #\{name} from object of different class",
)
}
return method_val
}
None => ()
}
_ => ()
}
// Fall back to private field lookup
get_private_field(obj, brand, name)
}
///|
/// Runtime Semantics: NamedEvaluation. Anonymous class expressions need the
/// inferred name during ClassDefinitionEvaluation (before static elements run),
/// while the other anonymous function forms can retain their existing
/// SetFunctionName path after ordinary evaluation.
fn Interpreter::eval_named_expr(
self : Interpreter,
ctx : ExecContext,
expr : @ast.Expr,
env : Environment,
inferred_name : String,
) -> Value raise Error {
match expr {
Grouping(inner, _) => self.eval_named_expr(ctx, inner, env, inferred_name)
ClassExpr(None, superclass, members, _, source_text) =>
self.create_class(
ctx, inferred_name, superclass, members, env, source_text,
)
_ => {
let value = self.eval_expr(ctx, expr, env)
if is_anonymous_function_definition(expr) {
set_function_name(value, inferred_name)
}
value
}
}
}
///|
fn Interpreter::eval_identifier_reference(
self : Interpreter,
ctx : ExecContext,
name : String,
env : Environment,
) -> Value raise Error {
@static_semantics.validate_strict_identifier_reference(ctx.strict, name)
env.get_with_strict(name, ctx.strict) catch {
@errors.ReferenceError(message~) =>
if message == "\{name} is not defined" {
// Fallback: check global object (globalThis) for properties set via this.x = y
match self.global_this {
Object(data) =>
match data.bag.properties.get(name) {
Some(v) => v
None =>
raise @errors.ReferenceError(message="\{name} is not defined")
}
_ => raise @errors.ReferenceError(message="\{name} is not defined")
}
} else {
raise @errors.ReferenceError(message~)
}
other => raise other
}
}
///|
fn Interpreter::eval_expr(
self : Interpreter,
ctx : ExecContext,
expr : @ast.Expr,
env : Environment,
) -> Value raise Error {
self.observe_execution_step()
match expr {
NumberLit(n, _, _) => Number(n)
StringLit(s, _, _, _) => String_(s)
BoolLit(b, _) => Bool(b)
NullLit(_) => Null
UndefinedLit(_) => Undefined
ArrayHole(_) => Undefined
Ident(name, _) => self.eval_identifier_reference(ctx, name, env)
Grouping(e, _) => self.eval_expr(ctx, e, env)
Binary(op, left, right, loc) =>
self.eval_binary(ctx, op, left, right, env, loc)
Unary(op, operand, loc) => self.eval_unary(ctx, op, operand, env, loc)
Assign(name, value_expr, _) => {
@static_semantics.validate_strict_assignment_target_name(ctx.strict, name)
let resolved_env = env.resolve_binding_env(name)
let value = self.eval_named_expr(ctx, value_expr, env, name)
// Check if the target is a non-writable property on the global object
// (e.g. undefined, NaN, Infinity). In strict mode this is a TypeError;
// in non-strict mode the assignment is silently ignored.
// Check both unresolvable references AND global-env bindings, since
// undefined/NaN/Infinity are installed as global env bindings.
let is_global_or_missing = match resolved_env {
None => true
Some(e) => e.parent is None
}
if is_global_or_missing && self.is_immutable_global(name) {
if ctx.strict {
raise @errors.TypeError(
message="Cannot assign to read only property '\{name}' of object '[object global]'",
)
}
return value
}
match resolved_env {
Some(target_env) =>
target_env.assign_resolved(name, value, ctx.strict) catch {
@errors.ReferenceError(message~) => {
// The pre-RHS resolved binding was deleted by RHS evaluation.
// In non-strict mode, fall back to implicit global; in strict
// mode, propagate the ReferenceError.
if ctx.strict {
raise @errors.ReferenceError(message~)
}
self.global.def(name, value, VarBinding)
self.mirror_to_global(name, value, configurable=true)
}
e => raise e
}
None => {
if ctx.strict {
raise @errors.ReferenceError(message="\{name} is not defined")
}
// In non-strict mode, assigning to an undeclared variable creates
// a global property (sloppy mode implicit global) using the
// reference resolution result captured before RHS evaluation.
self.global.def(name, value, VarBinding)
self.mirror_to_global(name, value, configurable=true)
}
}
value
}
Ternary(cond, then_expr, else_expr, _) =>
if is_truthy(self.eval_expr(ctx, cond, env)) {
self.eval_expr(ctx, then_expr, env)
} else {
self.eval_expr(ctx, else_expr, env)
}
Call(callee, args, loc) => self.eval_call(ctx, callee, args, env, loc)
Member(obj_expr, prop, loc) =>
self.eval_member(ctx, obj_expr, prop, env, loc)
FuncExpr(name, params, body, _, source_text) => {
validate_function_signature(ctx.strict, name, params, body)
let func_data : FuncData = {
name,
params,
body,
closure: env,
strict: is_function_strict(ctx.strict, body),
// Named FunctionExpression: §15.2.5 installs a self-name binding
// in a dedicated funcEnv. Anonymous FEs have no such binding.
has_name_binding: name is Some(_),
is_method: false,
source_text,
}
make_func(func_data)
}
GeneratorExpr(name, params, body, _, source_text) => {
validate_function_signature(ctx.strict, name, params, body)
let strict = is_function_strict(ctx.strict, body)
self.make_generator_function(
name,
params,
None,
body,
strict,
env,
has_name_binding=name is Some(_),
source_text~,
)
}
GeneratorExprExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
name,
params,
rest_param,
body,
)
let strict = is_function_strict(ctx.strict, body)
self.make_generator_function_ext(
name,
params,
rest_param,
body,
strict,
env,
has_name_binding=name is Some(_),
source_text~,
)
}
AsyncFuncExpr(name, params, body, _, source_text) => {
validate_function_signature(ctx.strict, name, params, body)
let strict = is_function_strict(ctx.strict, body)
self.make_async_function(
name,
params,
None,
body,
strict,
env,
has_name_binding=name is Some(_),
source_text~,
)
}
AsyncFuncExprExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
name,
params,
rest_param,
body,
)
let strict = is_function_strict(ctx.strict, body)
self.make_async_function_ext(
name,
params,
rest_param,
body,
strict,
env,
has_name_binding=name is Some(_),
source_text~,
)
}
AsyncArrowFunc(params, body, _, source_text) => {
validate_function_signature(ctx.strict, None, params, body)
let strict = is_function_strict(ctx.strict, body)
self.make_async_function(
None,
params,
None,
body,
strict,
env,
source_text~,
is_arrow=true,
)
}
AsyncArrowFuncExt(params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
None,
params,
rest_param,
body,
)
let strict = is_function_strict(ctx.strict, body)
self.make_async_function_ext(
None,
params,
rest_param,
body,
strict,
env,
source_text~,
is_arrow=true,
)
}
AsyncGeneratorExpr(name, params, body, _, source_text) => {
validate_function_signature(ctx.strict, name, params, body)
let strict = is_function_strict(ctx.strict, body)
self.make_async_generator_function(
name,
params,
None,
body,
strict,
env,
has_name_binding=name is Some(_),
source_text~,
)
}
AsyncGeneratorExprExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
name,
params,
rest_param,
body,
)
let strict = is_function_strict(ctx.strict, body)
self.make_async_generator_function_ext(
name,
params,
rest_param,
body,
strict,
env,
has_name_binding=name is Some(_),
source_text~,
)
}
AwaitExpr(argument, _) =>
// await acts like yield inside the generator that backs the async function
self.eval_yield(ctx, Some(argument), false, env)
YieldExpr(argument, delegate, _) =>
self.eval_yield(ctx, argument, delegate, env)
ObjectLit(props, obj_loc) => {
let properties : Map[String, Value] = Map([])
let symbol_properties : Map[Int, Value] = Map([])
let descriptors : Map[String, PropDescriptor] = Map([])
let symbol_descriptors : Map[Int, PropDescriptor] = Map([])
// Build the object eagerly so methods captured during the prop loop can
// reference it as [[HomeObject]] (ES2022 §13.2.5.5). The bag's Maps are
// shared by reference, so later inserts populate the live object; the
// mutable `prototype` field is updated when a __proto__ override is seen.
// This is what makes `super.x` resolve dynamically against
// `Object.getPrototypeOf(homeObject)` even after Object.setPrototypeOf is
// called on the literal.
let default_obj_proto = env.get("[[ObjectPrototype]]") catch { _ => Null }
let object_data : ObjectData = {
bag: {
properties,
symbol_properties,
descriptors,
symbol_descriptors,
internal_slots: Map([]),
host_slots: Map([]),
},
prototype: default_obj_proto,
callable: None,
class_name: "Object",
extensible: true,
arraybuffer_state: None,
}
let home_object : Value = Object(object_data)
let method_env = Environment::new(parent=Some(env))
method_env.def_builtin("[[HomeObject]]", home_object)
for prop in props {
// Handle spread property: { ...expr }
if prop.kind == @ast.Spread {
let spread_val = self.eval_expr(ctx, prop.key, env)
self.copy_object_spread_properties(home_object, spread_val, obj_loc)
continue
}
if prop.computed {
// Evaluate and canonicalize the computed key before the value.
let key_val = self.eval_expr(ctx, prop.key, env)
let prop_key = self.to_object_literal_property_key(key_val)
match prop_key {
Symbol(_) =>
// Symbol key - store in symbol_properties
match prop.kind {
Init => {
let val = self.eval_named_prop_value(
ctx, prop, method_env, prop_key,
)
apply_object_literal_data_property(home_object, prop_key, val)
}
Get => {
let getter = self.eval_prop_value(ctx, prop, method_env)
apply_object_literal_accessor_property(
home_object, prop_key, getter, true,
)
}
Set => {
let setter = self.eval_prop_value(ctx, prop, method_env)
apply_object_literal_accessor_property(
home_object, prop_key, setter, false,
)
}
Spread => () // handled above
}
_ =>
match prop.kind {
Init => {
let val = self.eval_named_prop_value(
ctx, prop, method_env, prop_key,
)
apply_object_literal_data_property(home_object, prop_key, val)
}
Get => {
let getter = self.eval_prop_value(ctx, prop, method_env)
apply_object_literal_accessor_property(
home_object, prop_key, getter, true,
)
}
Set => {
let setter = self.eval_prop_value(ctx, prop, method_env)
apply_object_literal_accessor_property(
home_object, prop_key, setter, false,
)
}
Spread => () // handled above
}
}
} else {
// Static key - extract from StringLit
let key_str = match prop.key {
StringLit(s, _, _, _) => s
Ident(name, _) => name
NumberLit(n, _, _) => {
let i = n.to_int()
if i.to_double() == n {
i.to_string()
} else {
n.to_string()
}
}
_ => "" // fallback
}
match prop.kind {
Init =>
// Handle __proto__ as special syntax per B.3.1
if key_str == "__proto__" {
let val = self.eval_prop_value(ctx, prop, method_env)
apply_object_literal_proto_property(home_object, val)
} else {
let val = self.eval_named_prop_value(
ctx,
prop,
method_env,
String_(key_str),
)
properties[key_str] = val
}
Get => {
let getter = self.eval_prop_value(ctx, prop, method_env)
apply_object_literal_accessor_property(
home_object,
String_(key_str),
getter,
true,
)
}
Set => {
let setter = self.eval_prop_value(ctx, prop, method_env)
apply_object_literal_accessor_property(
home_object,
String_(key_str),
setter,
false,
)
}
Spread => () // handled above
}
}
}
home_object
}
ArrayLit(elements, _) => {
let vals : Array[Value] = []
let hole_idxs : Array[Int] = []
for e in elements {
match e {
SpreadExpr(inner, spread_loc) => {
let val = self.eval_expr(ctx, inner, env)
// Use iterator protocol for spreading
let spread_vals = self.spread_iterable(val, spread_loc)
for v in spread_vals {
vals.push(v)
}
}
ArrayHole(_) => {
hole_idxs.push(vals.length())
vals.push(Undefined)
}
_ => vals.push(self.eval_expr(ctx, e, env))
}
}
make_array_with_holes(vals, hole_idxs)
}
ComputedMember(obj_expr, key_expr, loc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let key = self.eval_expr(ctx, key_expr, env)
self.get_computed_property(obj, key, loc)
}
MemberAssign(obj_expr, prop, value_expr, loc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let value = self.eval_expr(ctx, value_expr, env)
self.set_property(obj, prop, value, loc, strict=ctx.strict)
}
PrivateMemberAssign(obj_expr, name, value_expr, _) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let value = self.eval_expr(ctx, value_expr, env)
let brand = resolve_private_brand(env, name)
let _ = set_private_field(obj, brand, name, value)
value
}
ComputedAssign(obj_expr, key_expr, value_expr, loc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let key = self.eval_expr(ctx, key_expr, env)
let value = self.eval_expr(ctx, value_expr, env)
self.set_computed_property(obj, key, value, loc, strict=ctx.strict)
}
SuperMemberAssign(prop, value_expr, loc) => {
let this_val = eval_this_value(env)
let super_proto = self.resolve_super_target(env)
match super_proto {
Undefined =>
raise @errors.ReferenceError(
message="super.prop used but no [[SuperPrototype]] in scope at line \{loc.line}",
)
_ => {
let value = self.eval_expr(ctx, value_expr, env)
if super_proto is Null {
raise @errors.TypeError(
message="Cannot set property on null super base at line \{loc.line}",
)
}
self.set_property(
super_proto,
prop,
value,
loc,
strict=ctx.strict,
receiver=this_val,
)
}
}
}
SuperComputedAssign(key_expr, value_expr, loc) => {
let this_val = eval_this_value(env)
let key = self.eval_expr(ctx, key_expr, env)
let super_proto = self.resolve_super_target(env)
match super_proto {
Undefined =>
raise @errors.ReferenceError(
message="super[expr] used but no [[SuperPrototype]] in scope at line \{loc.line}",
)
_ => {
let value = self.eval_expr(ctx, value_expr, env)
if super_proto is Null {
raise @errors.TypeError(
message="Cannot set property on null super base at line \{loc.line}",
)
}
let prop_key = to_property_key(key, interp=Some(self))
self.set_computed_property(
super_proto,
prop_key,
value,
loc,
strict=ctx.strict,
receiver=this_val,
)
}
}
}
NewExpr(callee_expr, arg_exprs, loc) =>
self.eval_new(ctx, callee_expr, arg_exprs, env, loc)
ThisExpr(_) => eval_this_value(env)
UpdateExpr(op, operand, prefix, loc) =>
self.eval_update(ctx, op, operand, prefix, env, loc)
CompoundAssign(op, target, value_expr, loc) =>
self.eval_compound_assign(ctx, op, target, value_expr, env, loc)
Comma(left, right, _) => self.eval_direct_comma(ctx, left, right, env)
TemplateLit(quasis, exprs, _) => {
let buf = StringBuilder::new()
for i = 0; i < quasis.length(); i = i + 1 {
// Validator ensures cooked is always Some for untagged templates
buf.write_string(
match quasis[i].1 {
Some(s) => s
None =>
fail(
"unreachable: untagged TemplateLit with invalid escape reached evaluator; early-error validator should have rejected it",
)
},
)
if i < exprs.length() {
let val = self.eval_expr(ctx, exprs[i], env)
buf.write_string(to_js_string(val, interp=Some(self)))
}
}
String_(buf.to_string())
}
TaggedTemplate(tag_expr, quasis, exprs, loc) => {
let (tag, this_val) = match tag_expr {
Member(obj_expr, prop, member_loc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
(self.get_property(obj, prop, member_loc), obj)
}
ComputedMember(obj_expr, key_expr, member_loc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let key = self.eval_expr(ctx, key_expr, env)
(self.get_computed_property(obj, key, member_loc), obj)
}
_ => (self.eval_expr(ctx, tag_expr, env), Undefined)
}
// Build args: [templateObj, ...substitutions]
let args : Array[Value] = [self.get_tagged_template_object(loc, quasis)]
for e in exprs {
args.push(self.eval_expr(ctx, e, env))
}
self.call_value(tag, this_val, args, loc)
}
ArrowFunc(params, body, _, source_text) => {
validate_function_signature(ctx.strict, None, params, body)
let func_data : FuncData = {
name: None,
params,
body,
closure: env,
strict: is_function_strict(ctx.strict, body),
has_name_binding: false,
is_method: false,
source_text,
}
let nf_desc : PropDescriptor = {
writable: false,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
stamp_function_realm(
Object({
bag: {
properties: {
"name": String_(""),
"length": Number(params.length().to_double()),
},
symbol_properties: Map([]),
descriptors: { "name": nf_desc, "length": nf_desc },
symbol_descriptors: Map([]),
internal_slots: Map([]),
host_slots: Map([]),
},
prototype: get_func_proto(realm_state=Some(self.realm_state)),
callable: Some(ArrowFunc(func_data)),
class_name: "Function",
extensible: true,
arraybuffer_state: None,
}),
realm_state=Some(self.realm_state),
)
}
ArrowFuncExt(params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
None,
params,
rest_param,
body,
)
let func_data : FuncDataExt = {
name: None,
params,
rest_param,
body,
closure: env,
strict: is_function_strict(ctx.strict, body),
has_name_binding: false,
is_method: false,
source_text,
}
let nf_desc : PropDescriptor = {
writable: false,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
let arrow_len = expected_argument_count_ext(params)
stamp_function_realm(
Object({
bag: {
properties: {
"name": String_(""),
"length": Number(arrow_len.to_double()),
},
symbol_properties: Map([]),
descriptors: { "name": nf_desc, "length": nf_desc },
symbol_descriptors: Map([]),
internal_slots: Map([]),
host_slots: Map([]),
},
prototype: get_func_proto(realm_state=Some(self.realm_state)),
callable: Some(ArrowFuncExt(func_data)),
class_name: "Function",
extensible: true,
arraybuffer_state: None,
}),
realm_state=Some(self.realm_state),
)
}
FuncExprExt(name, params, rest_param, body, _, source_text) => {
validate_function_signature_ext(
ctx.strict,
name,
params,
rest_param,
body,
)
let func_data : FuncDataExt = {
name,
params,
rest_param,
body,
closure: env,
strict: is_function_strict(ctx.strict, body),
has_name_binding: name is Some(_),
is_method: false,
source_text,
}
make_func_ext(func_data)
}
RegexLit(pattern, flags, _) =>
(self.stdlib_hooks.make_regexp_object)(self.realm_state, pattern, flags)
SpreadExpr(_, _) =>
raise @errors.SyntaxError(
message="Spread expression used outside of call or array literal",
)
DestructureAssign(pattern, value_expr, _) => {
let value = self.eval_expr(ctx, value_expr, env)
self.eval_destructure_assign(ctx, pattern, value, env)
}
WebCompatCallAssign(call_expr, _, loc) => {
if ctx.strict {
raise @errors.SyntaxError(
message="Invalid assignment target at line \{loc.line}, col \{loc.col}",
)
}
let _ = self.eval_expr(ctx, call_expr, env)
raise @errors.ReferenceError(
message="Invalid left-hand side in assignment",
)
}
OptionalMember(obj_expr, prop, loc) => {
// obj?.prop - return undefined if obj is null/undefined
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => Undefined
_ => self.get_property(obj, prop, loc)
}
}
OptionalComputedMember(obj_expr, key_expr, loc) => {
// obj?.[key] - return undefined if obj is null/undefined
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => Undefined
_ => {
let key = self.eval_expr(ctx, key_expr, env)
self.get_computed_property(obj, key, loc)
}
}
}
ChainMember(_, _, _) | ChainComputedMember(_, _, _) => {
let (value, _) = self.eval_chain_expr(ctx, expr, env)
value
}
OptionalCall(callee_expr, arg_exprs, loc) => {
// func?.(args) - return undefined if func is null/undefined
// Per ES spec, arguments are NOT evaluated if function is nullish
// Need to preserve receiver for method calls
// Peel grouping parentheses to recover the reference/receiver (§13.2.9).
// Grouping preserves the Reference, so (a.b)?.() should call with this=a.
let unwrapped = unwrap_grouping(callee_expr)
match unwrapped {
OptionalMember(obj_expr, prop, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => Undefined
_ => {
let func_val = self.get_property(obj, prop, mloc)
match func_val {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(func_val, obj, args, loc)
}
}
}
}
}
OptionalComputedMember(obj_expr, key_expr, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => Undefined
_ => {
let key = self.eval_expr(ctx, key_expr, env)
let func_val = self.get_computed_property(obj, key, mloc)
match func_val {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(func_val, obj, args, loc)
}
}
}
}
}
ChainMember(obj_expr, prop, mloc) => {
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
Undefined
} else {
let func_val = self.get_property(obj, prop, mloc)
match func_val {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(func_val, obj, args, loc)
}
}
}
}
ChainComputedMember(obj_expr, key_expr, mloc) => {
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
Undefined
} else {
let key = self.eval_expr(ctx, key_expr, env)
let func_val = self.get_computed_property(obj, key, mloc)
match func_val {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(func_val, obj, args, loc)
}
}
}
}
Member(obj_expr, prop, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let func_val = self.get_property(obj, prop, mloc)
match func_val {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(func_val, obj, args, loc)
}
}
}
ComputedMember(obj_expr, key_expr, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let key = self.eval_expr(ctx, key_expr, env)
let func_val = self.get_computed_property(obj, key, mloc)
match func_val {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(func_val, obj, args, loc)
}
}
}
PrivateMember(obj_expr, name, _) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let func_val = get_private_member(obj, name, env)
match func_val {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(func_val, obj, args, loc)
}
}
}
SuperMember(prop, sloc) => {
let (this_val, func_val) = self.eval_super_property_call_reference(
env, prop, sloc,
)
match func_val {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(func_val, this_val, args, loc)
}
}
}
SuperComputedMember(key_expr, sloc) => {
let _ = eval_this_value(env)
let key = self.eval_expr(ctx, key_expr, env)
let (this_val, func_val) = self.eval_super_computed_call_reference(
env, key, sloc,
)
match func_val {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(func_val, this_val, args, loc)
}
}
}
_ => {
let callee = self.eval_expr(ctx, callee_expr, env)
match callee {
Null | Undefined => Undefined
_ => {
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.call_value(callee, Undefined, args, loc)
}
}
}
}
}
ClassExpr(name, superclass, methods, _, source_text) => {
let class_name = name.unwrap_or("")
self.create_class(
ctx,
class_name,
superclass,
methods,
env,
source_text,
class_binding=name,
)
}
SuperCall(arg_exprs, loc) => {
// SuperCall steps 3-5: read the active class function's current
// [[Prototype]] before evaluating Arguments, but defer IsConstructor
// validation until after argument evaluation.
let super_ctor = get_active_super_constructor(env, loc)
let args = self.eval_args_with_spread(ctx, arg_exprs, env)
self.eval_super_dispatch(super_ctor, args, env, loc)
}
SuperMember(prop, loc) => self.eval_super_property(env, prop, loc)
SuperComputedMember(key_expr, loc) => {
// Evaluate super binding first to ensure `this` TDZ/derived checks
// happen before computed-key side effects.
let _ = eval_this_value(env)
let key = self.eval_expr(ctx, key_expr, env)
self.eval_super_computed_property(env, key, loc)
}
NewTargetExpr(_) => eval_new_target_value(env)
PrivateIdent(name, _) => fail("TODO: bare private name reference: #\{name}")
PrivateMember(obj, name, _) => {
let obj_val = self.eval_expr(ctx, obj, env)
get_private_member(obj_val, name, env)
}
}
}
///|
// GetSuperConstructor reads the active class function's live [[Prototype]].
// The caller performs this before ArgumentListEvaluation, matching SuperCall's
// required observable ordering.
fn get_active_super_constructor(
env : Environment,
loc : @token.Loc,
) -> Value raise Error {
let active_function = env.get("[[ActiveClassFunction]]") catch {
_ =>
raise @errors.ReferenceError(
message=format_loc_context(
"super() called but no active class function is in scope", loc,
),
)
}
match active_function {
Object(data) => data.prototype
_ =>
raise @errors.ReferenceError(
message=format_loc_context(
"super() called but active class function is invalid", loc,
),
)
}
}
///|
fn Interpreter::construct_and_bind_super(
self : Interpreter,
super_ctor : Value,
args : Array[Value],
super_new_target : Value,
this_val : Value,
env : Environment,
loc : @token.Loc,
) -> Value raise Error {
let super_result = self.construct_value(
super_ctor,
args,
loc,
new_target=Some(super_new_target),
)
match super_result {
Object(_) | Array(_) | Map(_) | Set(_) | Promise(_) | Proxy(_) => {
env.initialize_in_chain("this", super_result)
if env.has("[[InitInstanceFields]]") {
let init_fn = env.get("[[InitInstanceFields]]")
let _ = self.call_value(init_fn, super_result, [], loc)
}
super_result
}
_ => {
env.initialize_in_chain("this", this_val)
if env.has("[[InitInstanceFields]]") {
let init_fn = env.get("[[InitInstanceFields]]")
let _ = self.call_value(init_fn, this_val, [], loc)
}
env.get("this")
}
}
}
///|
// Dispatch a super() call. The selected super constructor was captured before
// argument evaluation. This function validates it afterward, reads
// [[PendingThis]]/this and from `env`, runs the constructor,
// updates env.this, applies env.[[InitInstanceFields]], and returns the resolved
// this value. It calls itself recursively for implicit derived constructors.
fn Interpreter::eval_super_dispatch(
self : Interpreter,
super_ctor : Value,
args : Array[Value],
env : Environment,
loc : @token.Loc,
) -> Value raise Error {
if !is_constructor_value(super_ctor) {
raise @errors.TypeError(
message=format_loc_context("super constructor is not a constructor", loc),
)
}
let this_val = if env.has("[[PendingThis]]") {
env.get("[[PendingThis]]")
} else {
env.get("this")
}
let super_new_target = env.get("") catch { _ => Undefined }
match super_ctor {
Object(data) =>
match data.callable {
Some(_) =>
// All super constructors, including classes and built-ins, use the
// canonical [[Construct]] path. This keeps newTarget forwarding,
// allocation timing, return-value replacement, and field setup in a
// single implementation.
self.construct_and_bind_super(
super_ctor, args, super_new_target, this_val, env, loc,
)
_ =>
raise @errors.ReferenceError(
message=format_loc_context(
"super() called but the selected super value is not a constructor",
loc,
),
)
}
Proxy(_) =>
self.construct_and_bind_super(
super_ctor, args, super_new_target, this_val, env, loc,
)
Null =>
// `extends null`: super() attempts Construct(null, ...) → TypeError
raise @errors.TypeError(
message=format_loc_context("null is not a constructor", loc),
)
_ =>
raise @errors.ReferenceError(
message=format_loc_context(
"super() called but no super constructor is in scope", loc,
),
)
}
}
///|
fn Interpreter::eval_instanceof_values(
self : Interpreter,
l : Value,
r : Value,
loc : @token.Loc,
) -> Value raise Error {
match r {
Object(r_data) => {
// Step 1: GetMethod(C, @@hasInstance) — must invoke accessor getters per spec.
let has_instance_sym = self.realm_state.well_known_symbols.has_instance
match
lookup_symbol_property_chain(
r,
r_data,
has_instance_sym.id,
interp=Some(self),
) {
Some(Undefined) | Some(Null) | None =>
// No @@hasInstance — fall back to OrdinaryHasInstance.
// Step 2: Check if RHS is callable (required for OrdinaryHasInstance)
match r_data.callable {
None =>
raise @errors.TypeError(
message="Right-hand side of 'instanceof' is not callable",
)
Some(_) =>
// Step 3 of OrdinaryHasInstance: If O is not an Object, return
// false. MUST precede steps 4-5 so primitive LHS never triggers
// step 5's TypeError even when C.prototype is a non-object.
if !is_object_value(l) {
Bool(false)
} else {
// Steps 4-5: Get(C, "prototype"); if not Object, TypeError.
// Bound functions without own prototype property fall through
// to the None arm.
match r_data.bag.properties.get("prototype") {
Some(proto) =>
if is_object_value(proto) {
instanceof_prototype_chain(l, proto, self)
} else {
raise @errors.TypeError(
message="Function has non-object prototype in instanceof check",
)
}
None => Bool(false)
}
}
}
Some(has_instance_fn) => {
if !is_callable(has_instance_fn) {
raise @errors.TypeError(
message="Symbol.hasInstance is not a function",
)
}
let result = self.call_value(has_instance_fn, r, [l], loc)
Bool(is_truthy(result))
}
}
}
Proxy(proxy_data) => {
// Step 1: Check for Symbol.hasInstance on the proxy (via get trap)
let has_instance_sym = self.realm_state.well_known_symbols.has_instance
let has_instance_method = self.get_computed_property(
r,
Symbol(has_instance_sym),
loc,
)
match has_instance_method {
Undefined | Null => {
// Step 2: Fall back to prototype chain check
let target = get_proxy_target(proxy_data)
let proto = self.get_property(r, "prototype", loc)
match proto {
Object(_) => instanceof_prototype_chain(l, proto, self)
_ =>
// Check if target is callable
match target {
Object(t_data) =>
match t_data.callable {
Some(_) => Bool(false)
None =>
raise @errors.TypeError(
message="Right-hand side of 'instanceof' is not callable",
)
}
_ => Bool(false)
}
}
}
has_instance_fn => {
if !is_callable(has_instance_fn) {
raise @errors.TypeError(
message="Symbol.hasInstance is not a function",
)
}
let result = self.call_value(has_instance_fn, r, [l], loc)
Bool(is_truthy(result))
}
}
}
_ =>
raise @errors.TypeError(
message="Right-hand side of 'instanceof' is not an object",
)
}
}
///|
fn Interpreter::eval_binary(
self : Interpreter,
ctx : ExecContext,
op : @ast.BinOp,
left_expr : @ast.Expr,
right_expr : @ast.Expr,
env : Environment,
loc : @token.Loc,
) -> Value raise Error {
// Short-circuit for &&, ||, and ??
match op {
And => {
let left = self.eval_expr(ctx, left_expr, env)
if !is_truthy(left) {
return left
}
self.eval_expr(ctx, right_expr, env)
}
Or => {
let left = self.eval_expr(ctx, left_expr, env)
if is_truthy(left) {
return left
}
self.eval_expr(ctx, right_expr, env)
}
NullishCoalesce => {
// ?? returns left if it's not null/undefined, otherwise right
let left = self.eval_expr(ctx, left_expr, env)
match left {
Null | Undefined => self.eval_expr(ctx, right_expr, env)
_ => left
}
}
Instanceof => {
let l = self.eval_expr(ctx, left_expr, env)
let r = self.eval_expr(ctx, right_expr, env)
self.eval_instanceof_values(l, r, loc)
}
In =>
// Handle #x in obj — check private brand rather than property lookup
match left_expr {
PrivateIdent(_, _) => {
let right = self.eval_expr(ctx, right_expr, env)
if !is_object_value(right) {
raise @errors.TypeError(
message="Cannot use 'in' operator to search for '\{right.to_string()}' in \{type_of(right)}",
)
}
let brand = env.get("[[PrivateBrand]]") catch {
_ => return Bool(false)
}
Bool(has_brand(right, brand))
}
_ => {
let left = self.eval_expr(ctx, left_expr, env)
let right = self.eval_expr(ctx, right_expr, env)
if is_object_value(right) {
Bool(
self.has_property_key(
right,
to_property_key(left, interp=Some(self)),
),
)
} else {
raise @errors.TypeError(
message="Cannot use 'in' operator to search for '\{left.to_string()}' in \{type_of(right)}",
)
}
}
}
_ => {
let left = self.eval_expr(ctx, left_expr, env)
let right = self.eval_expr(ctx, right_expr, env)
eval_binary_op(op, left, right, loc, interp=Some(self))
}
}
}
///|
pub fn eval_unary_value_op(
op : @ast.UnaryOp,
value : Value,
loc : @token.Loc,
interp? : Interpreter? = None,
) -> Value raise Error {
match op {
Neg => Number(-to_number(value, interp~))
Pos => Number(to_number(value, interp~))
Not => Bool(!is_truthy(value))
BitNot => Number(to_int32(to_number(value, interp~)).lnot().to_double())
Typeof => String_(type_of(value))
Void | Delete =>
raise @errors.InternalError(
message="Unexpected unary operator at line \{loc.line}, col \{loc.col}",
)
}
}
///|
pub fn eval_binary_op(
op : @ast.BinOp,
left : Value,
right : Value,
_loc : @token.Loc,
interp? : Interpreter? = None,
) -> Value raise Error {
fn n(v : Value) -> Double raise Error {
to_number(v, interp~)
}
fn s(v : Value) -> String raise Error {
to_js_string(v, interp~)
}
fn p_default(v : Value, data : ObjectData) -> Value raise Error {
to_primitive_default(v, data, interp~)
}
match (op, left, right) {
// Symbol cannot be converted to string implicitly
(Add, Symbol(_), _) | (Add, _, Symbol(_)) =>
raise @errors.TypeError(
message="Cannot convert a Symbol value to a string",
)
// String concatenation (only for primitive operands)
(Add, String_(a), String_(b)) => String_(a + b)
(Add, String_(a), Number(b)) => String_(a + Number(b).to_string())
(Add, String_(a), Bool(b)) => String_(a + b.to_string())
(Add, String_(a), Null) => String_(a + "null")
(Add, String_(a), Undefined) => String_(a + "undefined")
(Add, Number(a), String_(b)) => String_(Number(a).to_string() + b)
(Add, Bool(a), String_(b)) => String_(a.to_string() + b)
(Add, Null, String_(b)) => String_("null" + b)
(Add, Undefined, String_(b)) => String_("undefined" + b)
// Numeric operations
(Add, Number(a), Number(b)) => Number(a + b)
(Sub, Number(a), Number(b)) => Number(a - b)
(Mul, Number(a), Number(b)) => Number(a * b)
(Div, Number(a), Number(b)) => Number(a / b)
(Mod, Number(a), Number(b)) => Number(a % b)
// Comparison (numeric)
(Lt, Number(a), Number(b)) => Bool(a < b)
(Gt, Number(a), Number(b)) => Bool(a > b)
(LtEq, Number(a), Number(b)) => Bool(a <= b)
(GtEq, Number(a), Number(b)) => Bool(a >= b)
// Comparison (string — lexicographic)
(Lt, String_(a), String_(b)) => Bool(a < b)
(Gt, String_(a), String_(b)) => Bool(a > b)
(LtEq, String_(a), String_(b)) => Bool(a <= b)
(GtEq, String_(a), String_(b)) => Bool(a >= b)
// Equality
(EqEqEq, l, r) => Bool(strict_equal(l, r))
(NotEqEq, l, r) => Bool(!strict_equal(l, r))
(EqEq, l, r) => Bool(loose_equal(l, r, interp~))
(NotEq, l, r) => Bool(!loose_equal(l, r, interp~))
// Exponentiation
(Exp, Number(a), Number(b)) => Number(@math.pow(a, b))
(Exp, l, r) => Number(@math.pow(n(l), n(r)))
// Add fallback: ToPrimitive, then check for strings
(Add, l, r) => {
// ToPrimitive with hint "default" per spec (+ operator uses no preferred type)
let lp = match l {
Object(data) => p_default(l, data)
Proxy(_) => to_primitive_via_dispatch(l, "default", interp~)
Array(arr_data) => {
let s = arr_data.elements.map(fn(v) { v.to_string() }).join(",")
String_(s)
}
_ => l
}
let rp = match r {
Object(data) => p_default(r, data)
Proxy(_) => to_primitive_via_dispatch(r, "default", interp~)
Array(arr_data) => {
let s = arr_data.elements.map(fn(v) { v.to_string() }).join(",")
String_(s)
}
_ => r
}
// If either is a string after ToPrimitive, do string concatenation
match (lp, rp) {
(String_(a), String_(b)) => String_(a + b)
(String_(a), b) => String_(a + s(b))
(a, String_(b)) => String_(s(a) + b)
(a, b) => Number(n(a) + n(b))
}
}
(Sub, l, r) => Number(n(l) - n(r))
(Mul, l, r) => Number(n(l) * n(r))
(Div, l, r) => Number(n(l) / n(r))
(Mod, l, r) => Number(n(l) % n(r))
// Comparison fallbacks (type coercion)
(Lt, l, r) => Bool(n(l) < n(r))
(Gt, l, r) => Bool(n(l) > n(r))
(LtEq, l, r) => Bool(n(l) <= n(r))
(GtEq, l, r) => Bool(n(l) >= n(r))
// Bitwise operations
(BitAnd, l, r) => Number((to_int32(n(l)) & to_int32(n(r))).to_double())
(BitOr, l, r) => Number((to_int32(n(l)) | to_int32(n(r))).to_double())
(BitXor, l, r) => Number((to_int32(n(l)) ^ to_int32(n(r))).to_double())
// Shift operations
(LShift, l, r) => {
let a = to_int32(n(l))
let shift = to_int32(n(r)) & 0x1f
Number((a << shift).to_double())
}
(RShift, l, r) => {
let a = to_int32(n(l))
let shift = to_int32(n(r)) & 0x1f
Number((a >> shift).to_double())
}
(URShift, l, r) => {
let a = to_int32(n(l))
let shift = to_int32(n(r)) & 0x1f
if shift == 0 {
if a < 0 {
Number(a.to_double() + 4294967296.0)
} else {
Number(a.to_double())
}
} else {
let shifted = a >> shift
let all_bits : Int = 0x7FFFFFFF
let mask = all_bits >> (shift - 1)
Number((shifted & mask).to_double())
}
}
// instanceof - ES2015+ with Symbol.hasInstance support (spec: 7.3.21 OrdinaryHasInstance)
(Instanceof, l, r) =>
match interp {
Some(ip) => ip.eval_instanceof_values(l, r, _loc)
None =>
raise @errors.InternalError(
message="Internal error: instanceof requires interpreter context",
)
}
// in operator - ES §7.3.11 HasProperty
(In, l, r) =>
if is_object_value(r) {
match interp {
Some(ip) =>
Bool(ip.has_property_key(r, to_property_key(l, interp=Some(ip))))
None => Bool(has_property(r, s(l)))
}
} else {
raise @errors.TypeError(
message="Cannot use 'in' operator to search for '\{l.to_string()}' in \{type_of(r)}",
)
}
// And/Or/?? already handled in eval_binary
(And, _, _) | (Or, _, _) | (NullishCoalesce, _, _) =>
raise @errors.InternalError(
message="Internal error: logical operators should be short-circuited",
)
}
}
///|
/// Helper for instanceof: walk prototype chain to check if l's prototype chain includes target_proto
pub fn instanceof_prototype_chain(
l : Value,
target_proto : Value,
interp : Interpreter,
) -> Value raise Error {
let mut current = match l {
Object(l_data) => l_data.prototype
Array(data) =>
match get_array_prototype_override(data) {
Some(proto) => proto
None => interp.realm_state.get_array_proto()
}
Map(data) =>
data.prototype.unwrap_or_else(fn() { interp.realm_state.get_map_proto() })
Set(data) =>
data.prototype.unwrap_or_else(fn() { interp.realm_state.get_set_proto() })
Promise(data) =>
data.prototype.unwrap_or_else(fn() {
interp.realm_state.get_promise_proto()
})
// Invoke the [[GetPrototypeOf]] trap so Proxy handler can intercept
Proxy(proxy_data) => proxy_get_prototype_of(interp, proxy_data)
// Primitive values are never instances
_ => return Bool(false)
}
let mut found = false
while true {
match current {
Null | Undefined => break
_ =>
if strict_equal(current, target_proto) {
found = true
break
} else {
match current {
Object(data) => current = data.prototype
// Invoke [[GetPrototypeOf]] trap when proxy appears in the chain
Proxy(proxy_data) =>
current = proxy_get_prototype_of(interp, proxy_data)
_ => break
}
}
}
}
Bool(found)
}
///|
pub fn get_array_prototype(realm_state : RealmState, arr : ArrayData) -> Value {
match get_array_prototype_override(arr) {
Some(proto) => proto
None => realm_state.get_array_proto()
}
}
///|
pub fn strict_equal(a : Value, b : Value) -> Bool {
match (a, b) {
(Number(a), Number(b)) => a == b
(String_(a), String_(b)) => a == b
(Bool(a), Bool(b)) => a == b
(Null, Null) => true
(Undefined, Undefined) => true
(Object(a), Object(b)) => physical_equal(a, b)
(Array(a), Array(b)) => physical_equal(a, b)
(Symbol(a), Symbol(b)) => a.id == b.id // Symbols compare by identity
(Map(a), Map(b)) => physical_equal(a, b) // Maps compare by reference
(Set(a), Set(b)) => physical_equal(a, b) // Sets compare by reference
(Promise(a), Promise(b)) => physical_equal(a, b) // Promises compare by reference
(Proxy(a), Proxy(b)) => physical_equal(a, b) // Proxies compare by reference
_ => false
}
}
///|
fn loose_equal(
a : Value,
b : Value,
interp? : Interpreter? = None,
) -> Bool raise Error {
// Abstract Equality Comparison Algorithm (ES spec §7.2.14)
match (a, b) {
// Same type: use strict equality
(Number(x), Number(y)) => x == y
(String_(x), String_(y)) => x == y
(Bool(x), Bool(y)) => x == y
(Null, Null) => true
(Undefined, Undefined) => true
(Object(x), Object(y)) => physical_equal(x, y)
(Array(x), Array(y)) => physical_equal(x, y)
(Symbol(x), Symbol(y)) => x.id == y.id
(Promise(x), Promise(y)) => physical_equal(x, y)
(Proxy(x), Proxy(y)) => physical_equal(x, y)
// null == undefined
(Null, Undefined) | (Undefined, Null) => true
// Number == String: convert string to number
(Number(n), String_(s)) => {
let num = to_number(String_(s)) catch { _ => return false }
n == num
}
(String_(s), Number(n)) => {
let num = to_number(String_(s)) catch { _ => return false }
num == n
}
// Boolean == anything: convert boolean to number first (§7.2.14 step 9/10)
(Bool(bval), _) =>
loose_equal(Number(if bval { 1.0 } else { 0.0 }), b, interp~)
(_, Bool(bval)) =>
loose_equal(a, Number(if bval { 1.0 } else { 0.0 }), interp~)
// Object == primitive: ToPrimitive with hint "default" (§7.2.14 steps 10-11)
(Object(data), Number(_))
| (Object(data), String_(_))
| (Object(data), Symbol(_)) => {
let prim = to_primitive_default(a, data, interp~)
loose_equal(prim, b, interp~)
}
(Number(_), Object(data))
| (String_(_), Object(data))
| (Symbol(_), Object(data)) => {
let prim = to_primitive_default(b, data, interp~)
loose_equal(a, prim, interp~)
}
// Array == primitive: ToPrimitive with hint "default" (§7.2.14 steps 10-11)
(Array(_), Number(_)) | (Array(_), String_(_)) | (Array(_), Symbol(_)) => {
let prim = to_primitive_default_array(a, interp~)
loose_equal(prim, b, interp~)
}
(Number(_), Array(_)) | (String_(_), Array(_)) | (Symbol(_), Array(_)) => {
let prim = to_primitive_default_array(b, interp~)
loose_equal(a, prim, interp~)
}
// Symbols don't coerce to other primitive types
(Symbol(_), _) | (_, Symbol(_)) => false
// Everything else is false
_ => false
}
}
///|
fn delete_string_property_from_bag(
bag : PropertyBag,
prop : String,
strict : Bool,
) -> Bool raise Error {
match bag.descriptors.get(prop) {
Some(desc) =>
if !desc.configurable {
if strict {
raise @errors.TypeError(message="Cannot delete property '\{prop}'")
}
return false
}
None => ()
}
let _ = bag.properties.remove(prop)
let _ = bag.descriptors.remove(prop)
true
}
///|
fn delete_symbol_property_from_bag(
bag : PropertyBag,
sym_id : Int,
strict : Bool,
) -> Bool raise Error {
match bag.symbol_descriptors.get(sym_id) {
Some(desc) =>
if !desc.configurable {
if strict {
raise @errors.TypeError(message="Cannot delete property")
}
return false
}
None => ()
}
let _ = bag.symbol_properties.remove(sym_id)
let _ = bag.symbol_descriptors.remove(sym_id)
true
}
///|
fn mark_array_hole_if_index(data : ArrayData, prop : String) -> Unit {
let idx = @string.parse_int(prop) catch { _ => -1 }
if idx >= 0 && idx.to_string() == prop && idx < data.elements.length() {
data.holes.set(idx, ())
data.elements[idx] = Undefined
}
}
///|
fn delete_typedarray_string_property(
interp : Interpreter,
data : ObjectData,
prop : String,
strict : Bool,
) -> Bool raise Error {
match classify_typedarray_string_key(prop) {
Some(idx) =>
if idx >= 0 &&
(interp.stdlib_hooks.typedarray_is_valid_index)(
data,
idx,
interp.realm_state,
) {
if strict {
raise @errors.TypeError(message="Cannot delete property '\{prop}'")
}
false
} else {
true
}
None => delete_string_property_from_bag(data.bag, prop, strict)
}
}
///|
pub fn Interpreter::delete_property_key(
self : Interpreter,
obj : Value,
key : Value,
strict? : Bool = false,
) -> Bool raise Error {
let prop_key = to_property_key(key, interp=Some(self))
match obj {
Proxy(proxy_data) => {
let result = proxy_delete_property_key(self, proxy_data, prop_key)
if !result && strict {
raise @errors.TypeError(message="Cannot delete property")
}
result
}
Object(data) =>
match prop_key {
Symbol(sym) => delete_symbol_property_from_bag(data.bag, sym.id, strict)
String_(prop) if is_typedarray_class(data.class_name) =>
delete_typedarray_string_property(self, data, prop, strict)
String_(prop) => delete_string_property_from_bag(data.bag, prop, strict)
_ => true
}
Array(data) =>
match prop_key {
Symbol(sym) => delete_symbol_property_from_bag(data.bag, sym.id, strict)
String_("length") => {
if strict {
raise @errors.TypeError(message="Cannot delete property 'length'")
}
false
}
String_(prop) => {
let result = delete_string_property_from_bag(data.bag, prop, strict)
if result {
mark_array_hole_if_index(data, prop)
}
result
}
_ => true
}
Map(data) =>
match prop_key {
Symbol(sym) => delete_symbol_property_from_bag(data.bag, sym.id, strict)
String_(prop) => delete_string_property_from_bag(data.bag, prop, strict)
_ => true
}
Set(data) =>
match prop_key {
Symbol(sym) => delete_symbol_property_from_bag(data.bag, sym.id, strict)
String_(prop) => delete_string_property_from_bag(data.bag, prop, strict)
_ => true
}
Promise(data) =>
match prop_key {
Symbol(sym) => delete_symbol_property_from_bag(data.bag, sym.id, strict)
String_(prop) => delete_string_property_from_bag(data.bag, prop, strict)
_ => true
}
Undefined | Null =>
raise @errors.TypeError(
message="Cannot convert undefined or null to object",
)
_ => true
}
}
///|
pub fn eval_delete_property(
interp : Interpreter,
obj : Value,
prop : String,
strict : Bool,
) -> Value raise Error {
Bool(interp.delete_property_key(obj, String_(prop), strict~))
}
///|
pub fn eval_delete_computed_property(
interp : Interpreter,
obj : Value,
key : Value,
strict : Bool,
) -> Value raise Error {
Bool(interp.delete_property_key(obj, key, strict~))
}
///|
fn env_delete_binding_ignoring_with(env : Environment, name : String) -> Bool {
if env.bindings.contains(name) {
let _ = env.bindings.remove(name)
true
} else {
match env.parent {
Some(parent) => env_delete_binding_ignoring_with(parent, name)
None => false
}
}
}
///|
pub fn eval_delete_identifier(
interp : Interpreter,
ctx : ExecContext,
env : Environment,
name : String,
) -> Value raise Error {
// delete of an unqualified identifier is a SyntaxError in strict mode
if ctx.strict {
raise @errors.SyntaxError(
message="Delete of an unqualified identifier in strict mode.",
)
}
// Check if this identifier is in a with-object first.
match env.find_with_object(name) {
Some(with_obj) => Bool(interp.delete_property_key(with_obj, String_(name)))
None =>
match env.resolve_binding_env(name) {
Some(target_env) =>
if target_env.has_marker_in_chain(eval_deletable_var_marker(name)) {
// Only delete if target_env actually has the eval-created var
// binding. resolve_binding_env may have returned a shadowing
// let/const from an inner scope — we must not delete that.
match target_env.bindings.get(name) {
Some(binding) if binding.kind == VarBinding => {
let _ = env_delete_binding_ignoring_with(target_env, name)
// Also remove the globalThis mirror if the target is the
// global environment. eval-created vars are mirrored to
// globalThis, and the deleted env binding alone would
// still be reachable via the global property fallback.
if target_env.parent is None {
match interp.global_this {
Object(data) => {
let _ = data.bag.properties.remove(name)
let _ = data.bag.descriptors.remove(name)
}
_ => ()
}
}
Bool(true)
}
_ => Bool(false)
}
} else if target_env.parent is None {
// Only delete if the binding is a VarBinding, not let/const.
// A global let/const is non-configurable and must not be
// deleted even when globalThis has a configurable property
// with the same name (e.g. after defineProperty).
match target_env.bindings.get(name) {
Some(binding) if binding.kind == VarBinding =>
match interp.global_this {
Object(data) =>
match data.bag.descriptors.get(name) {
Some(desc) if desc.configurable => {
let _ = env_delete_binding_ignoring_with(
target_env, name,
)
let _ = data.bag.properties.remove(name)
let _ = data.bag.descriptors.remove(name)
Bool(true)
}
_ => Bool(false)
}
_ => Bool(false)
}
_ => Bool(false)
}
} else {
Bool(false)
}
None =>
// In non-strict mode, check if the identifier is a non-configurable
// property on the global object (e.g. undefined, NaN, Infinity).
match interp.global_this {
Object(data) =>
match data.bag.descriptors.get(name) {
Some(desc) =>
if !desc.configurable {
Bool(false)
} else {
let _ = data.bag.properties.remove(name)
let _ = data.bag.descriptors.remove(name)
Bool(true)
}
None => {
let _ = data.bag.properties.remove(name)
Bool(true)
}
}
_ => Bool(true)
}
}
}
}
///|
/// Check whether `name` is a non-writable property on the global object
/// (undefined, NaN, Infinity). These are { writable:false } per ES spec §19.1.
fn Interpreter::is_immutable_global(self : Interpreter, name : String) -> Bool {
match self.global_this {
Object(data) =>
match data.bag.descriptors.get(name) {
Some(desc) => !desc.writable
None => false
}
_ => false
}
}
///|
fn Interpreter::eval_unary(
self : Interpreter,
ctx : ExecContext,
op : @ast.UnaryOp,
operand : @ast.Expr,
env : Environment,
loc : @token.Loc,
) -> Value raise Error {
match op {
Typeof =>
match operand {
Ident(name, _) => {
@static_semantics.validate_strict_identifier_reference(
ctx.strict,
name,
)
if env.has(name) {
String_(type_of(env.get(name)))
} else {
// Also check global object for properties
match self.global_this {
Object(data) =>
match data.bag.properties.get(name) {
Some(v) => String_(type_of(v))
None => String_("undefined")
}
_ => String_("undefined")
}
}
}
_ => String_(type_of(self.eval_expr(ctx, operand, env)))
}
Void => {
let _ = self.eval_expr(ctx, operand, env)
Undefined
}
Delete =>
match operand {
Member(obj_expr, prop, _) => {
let obj = self.eval_expr(ctx, obj_expr, env)
eval_delete_property(self, obj, prop, ctx.strict)
}
ComputedMember(obj_expr, key_expr, _) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let key = self.eval_expr(ctx, key_expr, env)
eval_delete_computed_property(self, obj, key, ctx.strict)
}
OptionalMember(obj_expr, prop, _) => {
// delete o?.x: evaluate obj_expr directly, check nullish, delete
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => Bool(true)
_ => eval_delete_property(self, obj, prop, ctx.strict)
}
}
OptionalComputedMember(obj_expr, key_expr, _) => {
// delete o?.[x]: evaluate obj_expr directly, check nullish, delete
let obj = self.eval_expr(ctx, obj_expr, env)
match obj {
Null | Undefined => Bool(true)
_ => {
let key = self.eval_expr(ctx, key_expr, env)
eval_delete_computed_property(self, obj, key, ctx.strict)
}
}
}
ChainMember(obj_expr, prop, _) => {
// delete chain-member: evaluate chain up to base object, delete prop
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
Bool(true)
} else {
eval_delete_property(self, obj, prop, ctx.strict)
}
}
ChainComputedMember(obj_expr, key_expr, _) => {
let (obj, short_circuited) = self.eval_chain_expr(ctx, obj_expr, env)
if short_circuited {
Bool(true)
} else {
let key = self.eval_expr(ctx, key_expr, env)
eval_delete_computed_property(self, obj, key, ctx.strict)
}
}
Ident(name, _) => eval_delete_identifier(self, ctx, env, name)
Grouping(inner, _) =>
// Preserve delete semantics: `delete (expr)` evaluates `expr`
// with delete semantics, not as a plain value read.
self.eval_unary(ctx, Delete, inner, env, loc)
SuperMember(_, _) => {
// Must evaluate the this-value before throwing, so TDZ and
// null/undefined checks for the super-base are performed.
let _ = eval_this_value(env)
raise @errors.ReferenceError(
message="Cannot delete a super property reference",
)
}
SuperComputedMember(key_expr, _) => {
let _ = eval_this_value(env)
let _ = self.eval_expr(ctx, key_expr, env)
raise @errors.ReferenceError(
message="Cannot delete a super property reference",
)
}
_ => {
let _ = self.eval_expr(ctx, operand, env)
Bool(true)
}
}
_ => {
let val = self.eval_expr(ctx, operand, env)
eval_unary_value_op(op, val, loc, interp=Some(self))
}
}
}
///|
fn format_loc_context(message : String, loc : @token.Loc) -> String {
message + " at line \{loc.line}, col \{loc.col}"
}