Skip to content
64 changes: 64 additions & 0 deletions crates/script/elidex-js/src/vm/coerce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,70 @@ pub(super) fn f64_to_uint32(n: f64) -> u32 {
result
}

// ---------------------------------------------------------------------------
// ToObject (ES2020 §7.1.13)
// ---------------------------------------------------------------------------

/// Convert a value to an Object. Throws TypeError for null/undefined.
/// Primitives are wrapped in their corresponding wrapper objects.
pub(super) fn to_object(vm: &mut VmInner, val: JsValue) -> Result<ObjectId, VmError> {
use super::shape;
use super::value::{Object, PropertyStorage};

match val {
JsValue::Object(id) => Ok(id),
JsValue::Null | JsValue::Undefined => Err(VmError::type_error(
"Cannot convert undefined or null to object",
)),
JsValue::Number(n) => {
let wrapper = vm.alloc_object(Object {
kind: ObjectKind::NumberWrapper(n),
storage: PropertyStorage::shaped(shape::ROOT_SHAPE),
prototype: vm.number_prototype,
extensible: true,
});
Ok(wrapper)
}
JsValue::String(s) => {
let wrapper = vm.alloc_object(Object {
kind: ObjectKind::StringWrapper(s),
storage: PropertyStorage::shaped(shape::ROOT_SHAPE),
prototype: vm.string_prototype,
extensible: true,
});
Ok(wrapper)
}
JsValue::Boolean(b) => {
let wrapper = vm.alloc_object(Object {
kind: ObjectKind::BooleanWrapper(b),
storage: PropertyStorage::shaped(shape::ROOT_SHAPE),
prototype: vm.boolean_prototype,
extensible: true,
});
Ok(wrapper)
}
JsValue::BigInt(id) => {
let wrapper = vm.alloc_object(Object {
kind: ObjectKind::BigIntWrapper(id),
storage: PropertyStorage::shaped(shape::ROOT_SHAPE),
prototype: vm.bigint_prototype,
extensible: true,
});
Ok(wrapper)
}
JsValue::Symbol(id) => {
let wrapper = vm.alloc_object(Object {
kind: ObjectKind::SymbolWrapper(id),
storage: PropertyStorage::shaped(shape::ROOT_SHAPE),
prototype: vm.symbol_prototype,
extensible: true,
});
Ok(wrapper)
}
JsValue::Empty => Err(VmError::type_error("Cannot convert value to object")),
}
}

// ---------------------------------------------------------------------------
// Strict Equality (ES2020 §7.2.16)
// ---------------------------------------------------------------------------
Expand Down
22 changes: 21 additions & 1 deletion crates/script/elidex-js/src/vm/coerce_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,30 @@ use super::VmInner;
/// Collect own enumerable string keys in ES spec order (§9.1.11.1):
/// array-index keys in ascending numeric order, then other string keys
/// in insertion order.
pub(crate) fn collect_own_keys_es_order(vm: &VmInner, obj_id: ObjectId) -> Vec<StringId> {
pub(crate) fn collect_own_keys_es_order(vm: &mut VmInner, obj_id: ObjectId) -> Vec<StringId> {
use super::value::ObjectKind;

let obj = vm.get_object(obj_id);

// StringWrapper: index properties "0".."n-1" are own enumerable properties
let string_len = if let ObjectKind::StringWrapper(sid) = obj.kind {
vm.strings.get(sid).len()
} else {
0
};

let mut index_keys: Vec<(u32, StringId)> = Vec::new();
let mut other_keys: Vec<StringId> = Vec::new();

// Add StringWrapper index keys
#[allow(clippy::cast_possible_truncation)]
for idx in 0..string_len {
let key_str = idx.to_string();
let sid = vm.strings.intern(&key_str);
index_keys.push((idx as u32, sid));
}

let obj = vm.get_object(obj_id);
for (k, attrs) in obj.storage.iter_keys(&vm.shapes) {
if !attrs.enumerable {
continue;
Expand All @@ -33,6 +52,7 @@ pub(crate) fn collect_own_keys_es_order(vm: &VmInner, obj_id: ObjectId) -> Vec<S
}

index_keys.sort_by_key(|(idx, _)| *idx);
index_keys.dedup_by_key(|(idx, _)| *idx);

let mut keys = Vec::with_capacity(index_keys.len() + other_keys.len());
keys.extend(index_keys.into_iter().map(|(_, sid)| sid));
Expand Down
3 changes: 2 additions & 1 deletion crates/script/elidex-js/src/vm/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ fn trace_work_list(
| ObjectKind::NumberWrapper(_)
| ObjectKind::StringWrapper(_)
| ObjectKind::BooleanWrapper(_)
| ObjectKind::BigIntWrapper(_) => {}
| ObjectKind::BigIntWrapper(_)
| ObjectKind::SymbolWrapper(_) => {}
}
}
}
Expand Down
22 changes: 17 additions & 5 deletions crates/script/elidex-js/src/vm/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ use super::value::{
};
use super::{NativeFn, VmInner};

/// §19.2.3 Function.prototype — accepts any arguments, returns undefined.
fn native_function_prototype_noop(
_ctx: &mut NativeContext<'_>,
_this: JsValue,
_args: &[JsValue],
) -> Result<JsValue, VmError> {
Ok(JsValue::Undefined)
}

impl VmInner {
// -- Global registration -------------------------------------------------

Expand Down Expand Up @@ -223,12 +232,15 @@ impl VmInner {
// Function.prototype — prototype for all function objects.
// Must be registered before any native function is created so that
// `create_native_function` can set the prototype automatically.
// Function.prototype is itself an ordinary object (not callable) per
// ES2020 §19.2.3 (the spec says it's a function that accepts any
// arguments and returns undefined, but an ordinary object suffices
// for prototype chain purposes).
// §19.2.3: Function.prototype is a callable function that accepts
// any arguments and returns undefined.
let fp_name = self.strings.intern("");
let func_proto = self.alloc_object(Object {
kind: ObjectKind::Ordinary,
kind: ObjectKind::NativeFunction(super::NativeFunction {
name: fp_name,
func: native_function_prototype_noop,
constructable: false,
}),
storage: PropertyStorage::shaped(shape::ROOT_SHAPE),
prototype: Some(obj_proto),
extensible: true,
Expand Down
41 changes: 4 additions & 37 deletions crates/script/elidex-js/src/vm/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,43 +231,10 @@ impl VmInner {
JsValue::Empty | JsValue::Undefined | JsValue::Null => {
JsValue::Object(self.global_object)
}
JsValue::Number(n) => {
let wrapper = self.alloc_object(super::value::Object {
kind: ObjectKind::NumberWrapper(n),
storage: super::value::PropertyStorage::shaped(super::shape::ROOT_SHAPE),
prototype: self.number_prototype,
extensible: true,
});
JsValue::Object(wrapper)
}
JsValue::String(s) => {
let wrapper = self.alloc_object(super::value::Object {
kind: ObjectKind::StringWrapper(s),
storage: super::value::PropertyStorage::shaped(super::shape::ROOT_SHAPE),
prototype: self.string_prototype,
extensible: true,
});
JsValue::Object(wrapper)
}
JsValue::Boolean(b) => {
let wrapper = self.alloc_object(super::value::Object {
kind: ObjectKind::BooleanWrapper(b),
storage: super::value::PropertyStorage::shaped(super::shape::ROOT_SHAPE),
prototype: self.boolean_prototype,
extensible: true,
});
JsValue::Object(wrapper)
}
JsValue::BigInt(id) => {
let wrapper = self.alloc_object(super::value::Object {
kind: ObjectKind::BigIntWrapper(id),
storage: super::value::PropertyStorage::shaped(super::shape::ROOT_SHAPE),
prototype: self.bigint_prototype,
extensible: true,
});
JsValue::Object(wrapper)
}
_ => this,
// All primitives are wrapped via ToObject
other => JsValue::Object(
super::coerce::to_object(self, other).expect("primitive wrapping cannot fail"),
),
}
}

Expand Down
Loading
Loading