///|
/// Prototype internal operations (`[[GetPrototypeOf]]` / `[[SetPrototypeOf]]`)
/// relocated from `interpreter/stdlib/builtins_reflect.mbt` so that stdlib calls
/// a runtime operation instead of reaching into PropertyBag / ArrayData /
/// prototype / extensibility representation internals (architecture redesign
/// Stage 8, following #335 own-property-keys and #338 integrity ops).
///
/// These operations currently handle Object / Array / Proxy targets. The
/// remaining latent asymmetry is that Map / Set / Promise targets raise
/// TypeError even though their `[[GetPrototypeOf]]` / `[[SetPrototypeOf]]`
/// operations should succeed. Object.getPrototypeOf / Object.setPrototypeOf
/// carry a richer coercing dispatch and intentionally do not route through
/// these operations. Aligning the Map/Set/Promise arms is behavior-changing
/// work tracked for the enumerator-unification / internal-slot follow-ups
/// (#336 / #337).
///|
/// Module-namespace exotic object `[[SetPrototypeOf]]` shortcut (§28.3 /
/// §10.4.6.2). Relocated from `interpreter/stdlib/module_namespace_helpers.mbt`;
/// the `stdlib_` name prefix is dropped because runtime is now the defining
/// package (necessary move fixup, not a redesign). Shared by
/// Reflect.setPrototypeOf and Object.setPrototypeOf.
pub fn module_namespace_set_prototype_result(
data : ObjectData,
proto : Value,
) -> Bool? {
guard data.class_name == "Module" else { return None }
// A module namespace exotic object accepts [[SetPrototypeOf]] only when the
// new prototype is null (§10.4.6.2); any other proto fails.
Some(proto is Null)
}
///|
/// OrdinarySetPrototypeOf cycle decision (§10.1.2.1 steps 5-7). The walk
/// follows Value variants whose [[GetPrototypeOf]] is ordinary and stops at
/// Proxy, whose internal method is exotic. A repeated ordinary object means
/// the input chain already contains a cycle, which is rejected to guarantee
/// termination. The visited state is local; this helper has no external effect.
fn ordinary_prototype_would_cycle(
interp : Interpreter,
obj : Value,
proto : Value,
) -> Bool {
let mut cursor = proto
let visited : Array[Value] = []
while true {
if strict_equal(cursor, obj) {
return true
}
match cursor {
Null | Proxy(_) => return false
Object(_) | Array(_) | Map(_) | Set(_) | Promise(_) => ()
_ => return false
}
for seen in visited {
if strict_equal(seen, cursor) {
return true
}
}
visited.push(cursor)
match cursor {
Object(data) => cursor = data.prototype
Array(data) => cursor = get_array_prototype(interp.realm_state, data)
Map(data) =>
cursor = data.prototype.unwrap_or_else(fn() {
interp.realm_state.get_map_proto()
})
Set(data) =>
cursor = data.prototype.unwrap_or_else(fn() {
interp.realm_state.get_set_proto()
})
Promise(data) =>
cursor = data.prototype.unwrap_or_else(fn() {
interp.realm_state.get_promise_proto()
})
_ => return false
}
}
false
}
///|
/// [[SetPrototypeOf]] cycle detection and extensibility guard for ordinary objects
/// (§10.1.2.1). Returns true if the prototype was set, false if it would create a
/// cycle or the target is non-extensible. Does NOT handle immutable-prototype
/// exotic objects (Object.prototype) — callers check before reaching this.
pub fn ordinary_set_prototype(
interp : Interpreter,
data : ObjectData,
proto : Value,
) -> Bool {
if strict_equal(proto, data.prototype) {
return true
}
if !data.extensible {
return false
}
if ordinary_prototype_would_cycle(interp, Object(data), proto) {
return false
}
data.prototype = proto
true
}
///|
pub fn object_get_prototype_of(
interp : Interpreter,
target : Value,
) -> Value raise Error {
match target {
Object(data) => data.prototype
Array(data) => get_array_prototype(interp.realm_state, data)
Value::Proxy(proxy_data) => proxy_get_prototype_of(interp, proxy_data)
_ =>
raise @errors.TypeError(
message="Reflect.getPrototypeOf called on non-object",
)
}
}
///|
/// target.`[[SetPrototypeOf]]`(proto) backing for Reflect.setPrototypeOf
/// (§28.1.13). Argument-count validation stays at the stdlib boundary; this op
/// performs the proto-type validation and the `match target` dispatch verbatim,
/// including the non-object TypeError arm and the Reflect-specific "return false"
/// (rather than throw) on a non-extensible target.
pub fn object_set_prototype_of(
interp : Interpreter,
target : Value,
proto : Value,
) -> Value raise Error {
guard proto is (Object(_) | Proxy(_) | Null) else {
raise @errors.TypeError(
message="Object prototype may only be an Object or null",
)
}
match target {
Value::Proxy(proxy_data) =>
Bool(proxy_set_prototype_of(interp, proxy_data, proto))
Object(data) =>
match module_namespace_set_prototype_result(data, proto) {
Some(result) => Value::Bool(result)
None => Value::Bool(ordinary_set_prototype(interp, data, proto))
}
Array(data) => {
let current = get_array_prototype(interp.realm_state, data)
if strict_equal(proto, current) {
Value::Bool(true)
} else if !data.extensible {
Value::Bool(false)
} else if ordinary_prototype_would_cycle(interp, Array(data), proto) {
Value::Bool(false)
} else {
set_array_prototype_override(data, proto)
Value::Bool(true)
}
}
_ =>
raise @errors.TypeError(
message="Reflect.setPrototypeOf called on non-object",
)
}
}