///|
/// Standard descriptor metadata for built-in prototype methods installed as
/// ordinary data properties.
pub fn builtin_method_desc() -> PropDescriptor {
{
writable: true,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
}
///|
fn builtin_accessor_desc(getter : Value?, setter : Value?) -> PropDescriptor {
{
writable: false,
enumerable: false,
configurable: true,
getter,
setter,
is_accessor: true,
}
}
///|
fn builtin_non_writable_desc() -> PropDescriptor {
{
writable: false,
enumerable: false,
configurable: true,
getter: None,
setter: None,
is_accessor: false,
}
}
///|
fn builtin_frozen_data_desc() -> PropDescriptor {
{
writable: false,
enumerable: false,
configurable: false,
getter: None,
setter: None,
is_accessor: false,
}
}
///|
/// Install a built-in string-keyed method and its standard method descriptor.
pub fn install_builtin_method(
data : ObjectData,
name : String,
func : Value,
) -> Unit {
data.bag.properties[name] = func
data.bag.descriptors[name] = builtin_method_desc()
}
///|
/// Install a built-in string-keyed accessor property.
/// At least one of `getter` / `setter` must be `Some`; `(None, None)` aborts.
/// Use `None` for an absent side (not `Some(Undefined)`), unless intentionally
/// mimicking a JS `{ get: undefined }` / `{ set: undefined }` descriptor.
pub fn install_builtin_accessor(
data : ObjectData,
name : String,
getter : Value?,
setter : Value?,
) -> Unit {
guard getter is Some(_) || setter is Some(_) else {
abort("install_builtin_accessor: '\{name}' has neither getter nor setter")
}
data.bag.properties[name] = Undefined
data.bag.descriptors[name] = builtin_accessor_desc(getter, setter)
}
///|
/// Install a built-in string-keyed non-writable configurable data property.
pub fn install_builtin_non_writable(
data : ObjectData,
name : String,
value : Value,
) -> Unit {
data.bag.properties[name] = value
data.bag.descriptors[name] = builtin_non_writable_desc()
}
///|
/// Install a built-in string-keyed non-writable non-configurable data property.
pub fn install_builtin_frozen_data(
data : ObjectData,
name : String,
value : Value,
) -> Unit {
data.bag.properties[name] = value
data.bag.descriptors[name] = builtin_frozen_data_desc()
}
///|
/// Install a built-in symbol-keyed method and its standard method descriptor.
pub fn install_builtin_symbol_method(
data : ObjectData,
sym_id : Int,
func : Value,
) -> Unit {
data.bag.symbol_properties[sym_id] = func
data.bag.symbol_descriptors[sym_id] = builtin_method_desc()
}
///|
/// Install a built-in symbol-keyed non-writable non-configurable data property.
pub fn install_builtin_symbol_frozen_data(
data : ObjectData,
sym_id : Int,
value : Value,
) -> Unit {
data.bag.symbol_properties[sym_id] = value
data.bag.symbol_descriptors[sym_id] = builtin_frozen_data_desc()
}
///|
/// Install a built-in symbol-keyed accessor property with no setter.
pub fn install_builtin_symbol_accessor(
data : ObjectData,
sym_id : Int,
getter : Value,
) -> Unit {
data.bag.symbol_properties[sym_id] = Undefined
data.bag.symbol_descriptors[sym_id] = builtin_accessor_desc(
Some(getter),
None,
)
}
///|
/// Install a built-in symbol-keyed non-writable configurable string data property.
pub fn install_builtin_symbol_string(
data : ObjectData,
sym_id : Int,
tag : String,
) -> Unit {
data.bag.symbol_properties[sym_id] = Value::String_(tag)
data.bag.symbol_descriptors[sym_id] = builtin_non_writable_desc()
}
///|
fn ctor_object_data(ctor : Value, ctor_name : String) -> ObjectData {
guard ctor is Object(data) else {
abort("built-in constructor must be an object: \{ctor_name}")
}
data
}
///|
fn pin_realm_cache_and_register_builtin(
env : Environment,
realm_proto_cache : Ref[Value?],
ctor_name : String,
ctor : Value,
proto : Value,
) -> Unit {
realm_proto_cache.val = Some(proto)
env.def_builtin(ctor_name, ctor)
}
///|
fn install_registered_proto_constructor(
env : Environment,
ctor_name : String,
proto : Value,
) -> Unit {
let registered_ctor = match env.bindings.get(ctor_name) {
Some(binding) => binding.value
None =>
abort("built-in constructor missing from env after install: \{ctor_name}")
}
match proto {
Object(data) => install_builtin_method(data, "constructor", registered_ctor)
_ => ()
}
}
///|
fn assert_ctor_bagged_prototype_matches(
ctor : Value,
proto : Value,
ctor_name : String,
) -> Unit {
let data = ctor_object_data(ctor, ctor_name)
match data.bag.properties.get("prototype") {
Some(bagged) =>
match (bagged, proto) {
(Object(bagged_data), Object(proto_data)) =>
if !physical_equal(bagged_data, proto_data) {
abort(
"constructor bag .prototype must match pinned proto: \{ctor_name}",
)
}
_ =>
abort(
"constructor bag .prototype must be an object value: \{ctor_name}",
)
}
None => abort("constructor bag missing .prototype property: \{ctor_name}")
}
}
///|
/// How the constructor object's `.prototype` property is prepared before the
/// cache-for-X install pins `proto` and registers `ctor` in `env`.
pub(all) enum BuiltinCtorPrototypeInstall {
/// Install a frozen data `.prototype` on the constructor bag at install time
/// (Map/Set, WeakMap/WeakSet, …).
FrozenAtInstall
/// `.prototype` is already on the constructor bag with its descriptor; only
/// assert it matches `proto` (Array, boxed primitives, …).
PreBagged
/// `.prototype` descriptor is predeclared; assign `proto` at install time
/// (Promise, …).
AssignAtInstall
}
///|
fn apply_ctor_prototype_install(
ctor : Value,
ctor_name : String,
proto : Value,
mode : BuiltinCtorPrototypeInstall,
) -> Unit {
match mode {
FrozenAtInstall => {
let data = ctor_object_data(ctor, ctor_name)
install_builtin_frozen_data(data, "prototype", proto)
}
PreBagged => assert_ctor_bagged_prototype_matches(ctor, proto, ctor_name)
AssignAtInstall => {
let data = ctor_object_data(ctor, ctor_name)
match data.bag.descriptors.get("prototype") {
None => abort("constructor missing .prototype descriptor: \{ctor_name}")
Some(_) => ()
}
data.bag.properties["prototype"] = proto
}
}
}
///|
/// Cache-for-X install contract (#504 / #512): atomically pin `proto` in the
/// realm dispatch cache (`realm_proto_cache`), prepare the constructor's
/// `.prototype` property per `prototype_install`, and register `ctor` in `env`.
///
/// **Invariant:** `realm_proto_cache.val` must equal the constructor object's
/// `.prototype` property in every realm. Splitting these updates causes silent
/// dual-source dispatch bugs (PR #138).
///
/// Set `wire_proto_constructor` when the family also needs
/// `proto.constructor = ctor` (Map/Set, Array, boxed primitives, Promise, …).
///
/// **Migrated families:** Map/Set, WeakMap/WeakSet, Array, Promise, boxed
/// primitives (String/Number/Boolean/Symbol). #512 rollout complete.
pub fn install_realm_pinned_builtin_constructor(
env : Environment,
realm_proto_cache : Ref[Value?],
ctor_name : String,
ctor : Value,
proto : Value,
prototype_install? : BuiltinCtorPrototypeInstall = FrozenAtInstall,
wire_proto_constructor? : Bool = false,
) -> Unit {
apply_ctor_prototype_install(ctor, ctor_name, proto, prototype_install)
pin_realm_cache_and_register_builtin(
env, realm_proto_cache, ctor_name, ctor, proto,
)
if wire_proto_constructor {
install_registered_proto_constructor(env, ctor_name, proto)
}
}