Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion crates/perry-codegen-js/src/emit/exprs_more.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1606,13 +1606,20 @@ impl JsEmitter {
self.emit_expr(receiver);
self.output.push(')');
}
Expr::ReflectSet { target, key, value } => {
Expr::ReflectSet {
target,
key,
value,
receiver,
} => {
self.output.push_str("Reflect.set(");
self.emit_expr(target);
self.output.push_str(", ");
self.emit_expr(key);
self.output.push_str(", ");
self.emit_expr(value);
self.output.push_str(", ");
self.emit_expr(receiver);
self.output.push(')');
}
Expr::ReflectHas { target, key } => {
Expand Down
14 changes: 12 additions & 2 deletions crates/perry-codegen/src/expr/proxy_reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,14 +416,24 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
&[(DOUBLE, &t), (DOUBLE, &k), (DOUBLE, &r)],
))
}
Expr::ReflectSet { target, key, value } => {
Expr::ReflectSet {
target,
key,
value,
receiver,
} => {
// Pass the optional receiver through; the runtime defaults an
// `undefined` receiver to the target. A receiver distinct from an
// Integer-Indexed target redirects the write to the receiver per
// OrdinarySet (test262 internals/Set/key-is-valid-index-reflect-set).
let t = lower_expr(ctx, target)?;
let k = lower_expr(ctx, key)?;
let v = lower_expr(ctx, value)?;
let r = lower_expr(ctx, receiver)?;
Ok(ctx.block().call(
DOUBLE,
"js_reflect_set",
&[(DOUBLE, &t), (DOUBLE, &k), (DOUBLE, &v)],
&[(DOUBLE, &t), (DOUBLE, &k), (DOUBLE, &v), (DOUBLE, &r)],
))
}
Expr::PutValueSet {
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/runtime_decls/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ pub fn declare_phase_b_objects(module: &mut LlModule) {
module.declare_function("js_proxy_construct", DOUBLE, &[DOUBLE, DOUBLE, DOUBLE]);
module.declare_function("js_reflect_construct", DOUBLE, &[DOUBLE, DOUBLE, DOUBLE]);
module.declare_function("js_reflect_get", DOUBLE, &[DOUBLE, DOUBLE, DOUBLE]);
module.declare_function("js_reflect_set", DOUBLE, &[DOUBLE, DOUBLE, DOUBLE]);
module.declare_function("js_reflect_set", DOUBLE, &[DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
module.declare_function(
"js_put_value_set",
DOUBLE,
Expand Down
5 changes: 5 additions & 0 deletions crates/perry-hir/src/ir/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2371,6 +2371,11 @@ pub enum Expr {
target: Box<Expr>,
key: Box<Expr>,
value: Box<Expr>,
/// Optional `receiver` argument (4th): the object actually written
/// when the target's own/inherited descriptor allows it (observable
/// for Integer-Indexed exotic targets). Lowering supplies `target`
/// when the call omits it.
receiver: Box<Expr>,
},
/// Assignment PutValue for property references. Evaluates target/key/value
/// in source order, performs ordinary [[Set]] with an explicit receiver,
Expand Down
4 changes: 4 additions & 0 deletions crates/perry-hir/src/lower/expr_call/native_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,10 +1087,14 @@ pub(super) fn try_native_module_methods(
let target = it.next().unwrap_or(Expr::Undefined);
let key = it.next().unwrap_or(Expr::Undefined);
let value = it.next().unwrap_or(Expr::Undefined);
// Optional `receiver` (4th arg): default `undefined`
// and the runtime substitutes `target`.
let receiver = it.next().unwrap_or(Expr::Undefined);
return Ok(Ok(Expr::ReflectSet {
target: Box::new(target),
key: Box::new(key),
value: Box::new(value),
receiver: Box::new(receiver),
}));
}
"has" => {
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-hir/src/stable_hash/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ impl SH for Expr {
Expr::ProxyRevocable { target, handler } => { tag(h, 431); target.as_ref().hash(h); handler.as_ref().hash(h); }
Expr::ProxyRevoke(e) => { tag(h, 432); e.as_ref().hash(h); }
Expr::ReflectGet { target, key, receiver } => { tag(h, 433); target.as_ref().hash(h); key.as_ref().hash(h); receiver.as_ref().hash(h); }
Expr::ReflectSet { target, key, value } => { tag(h, 434); target.as_ref().hash(h); key.as_ref().hash(h); value.as_ref().hash(h); }
Expr::ReflectSet { target, key, value, receiver } => { tag(h, 434); target.as_ref().hash(h); key.as_ref().hash(h); value.as_ref().hash(h); receiver.as_ref().hash(h); }
Expr::PutValueSet { target, key, value, receiver, strict } => { tag(h, 12235); target.as_ref().hash(h); key.as_ref().hash(h); value.as_ref().hash(h); receiver.as_ref().hash(h); strict.hash(h); }
Expr::WithGet { object, property, fallback } => { tag(h, 12236); object.as_ref().hash(h); property.hash(h); fallback.as_ref().hash(h); }
Expr::WithSet { object, property, value, fallback, strict } => { tag(h, 12237); object.as_ref().hash(h); property.hash(h); value.as_ref().hash(h); hash_with_set_fallback(h, fallback); strict.hash(h); }
Expand Down
8 changes: 7 additions & 1 deletion crates/perry-hir/src/walker/expr_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1708,10 +1708,16 @@ where
f(target);
f(key);
}
Expr::ReflectSet { target, key, value } => {
Expr::ReflectSet {
target,
key,
value,
receiver,
} => {
f(target);
f(key);
f(value);
f(receiver);
}
Expr::PutValueSet {
target,
Expand Down
8 changes: 7 additions & 1 deletion crates/perry-hir/src/walker/expr_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1679,10 +1679,16 @@ where
f(target);
f(key);
}
Expr::ReflectSet { target, key, value } => {
Expr::ReflectSet {
target,
key,
value,
receiver,
} => {
f(target);
f(key);
f(value);
f(receiver);
}
Expr::PutValueSet {
target,
Expand Down
5 changes: 5 additions & 0 deletions crates/perry-runtime/src/array/iter_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,11 @@ pub extern "C" fn js_array_join_value(
let separator = if separator_value.to_bits() == crate::value::TAG_UNDEFINED {
ptr::null()
} else {
// `ToString(separator)`: a Symbol separator throws a TypeError
// (§7.1.17) instead of rendering as "Symbol(…)".
if unsafe { crate::symbol::js_is_symbol(separator_value) } != 0 {
crate::collection_iter::throw_type_error("Cannot convert a Symbol value to a string");
}
crate::value::js_jsvalue_to_string(separator_value) as *const crate::string::StringHeader
};
js_array_join(arr, separator)
Expand Down
28 changes: 25 additions & 3 deletions crates/perry-runtime/src/array/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,24 @@ pub extern "C" fn js_array_indexOf_jsvalue(
if arr.is_null() {
return -1;
}
// TypedArray: strict-equality numeric search over the typed store.
// TypedArray: strict-equality search over the typed store. `len == 0`
// returns BEFORE ToIntegerOrInfinity(fromIndex) per spec (an observable /
// throwing coercion never runs on an empty array). `js_jsvalue_equals`
// (not `==`) so BigInt elements compare by value — `js_typed_array_get`
// on a BigInt64Array returns a freshly boxed BigInt whose bit pattern
// never raw-equals the search value.
if let Some((ta, len)) = as_typed_array(arr) {
if len == 0 {
return -1;
}
let start = match forward_start_index(len as i64, from_index, has_from) {
Some(s) => s as i32,
None => return -1,
};
for i in start..len {
if crate::typedarray::js_typed_array_get(ta, i) == value {
if crate::value::js_jsvalue_equals(crate::typedarray::js_typed_array_get(ta, i), value)
== 1
{
return i;
}
}
Expand Down Expand Up @@ -206,7 +216,11 @@ pub extern "C" fn js_array_last_index_of_jsvalue(
};
let mut i = start;
while i >= 0 {
if crate::typedarray::js_typed_array_get(ta, i as i32) == value {
if crate::value::js_jsvalue_equals(
crate::typedarray::js_typed_array_get(ta, i as i32),
value,
) == 1
{
return i as i32;
}
i -= 1;
Expand Down Expand Up @@ -295,6 +309,9 @@ pub extern "C" fn js_array_includes_jsvalue(
// TypedArray: SameValueZero numeric search (so includes(NaN) is true for
// float typed arrays).
if let Some((ta, len)) = as_typed_array(arr) {
if len == 0 {
return 0;
}
let start = match forward_start_index(len as i64, from_index, has_from) {
Some(s) => s as i32,
None => return 0,
Expand All @@ -309,6 +326,11 @@ pub extern "C" fn js_array_includes_jsvalue(
}
unsafe {
let length = (*arr).length as i64;
// §23.1.3.16 step 3: `len == 0 → false` BEFORE ToIntegerOrInfinity
// (fromIndex), mirroring `indexOf`.
if length == 0 {
return 0;
}
let start = match forward_start_index(length, from_index, has_from) {
Some(s) => s,
None => return 0,
Expand Down
13 changes: 13 additions & 0 deletions crates/perry-runtime/src/builtins/numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,19 @@ pub extern "C" fn js_number_coerce(value: f64) -> f64 {
let joined = unsafe { crate::array::js_array_join(arr_ptr, comma) };
return js_number_coerce(crate::value::js_nanbox_string(joined as i64));
}
// TypedArray → OrdinaryToPrimitive(number): a *patched own*
// `valueOf`/`toString` expando (stored in the typed-array own-props
// side table, invisible to the generic object helpers below) runs
// first, with `this` = the typed array and abrupt completions
// propagating (test262 ctors/object-arg/throws-setting-obj-*). With
// no patch, fall through to the generic path (join + ToNumber).
if crate::typedarray::lookup_typed_array_kind(id as usize).is_some() {
if let Some(p) = unsafe {
crate::typedarray_props::typed_array_own_to_primitive_number(id as usize, value)
} {
return js_number_coerce(p);
}
}
// Object → consult [Symbol.toPrimitive]("number") first; if the
// object has a custom toPrimitive method, recurse with the result.
let primitive = unsafe { crate::symbol::js_to_primitive(value, 1) };
Expand Down
49 changes: 47 additions & 2 deletions crates/perry-runtime/src/object/field_get_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2236,8 +2236,12 @@ pub extern "C" fn js_object_has_property(obj: f64, key: f64) -> f64 {
if key_val.is_any_string() {
let key_str =
crate::value::js_get_string_pointer_unified(key) as *const crate::StringHeader;
// `in` is [[HasProperty]], not [[HasOwnProperty]] — ordinary
// keys consult the prototype chain (`"subarray" in ta`,
// inherited `Object.prototype` expandos), while canonical
// numeric indices stay bounds-only.
let present =
unsafe { crate::typedarray_props::typed_array_has_own_property(ta, key_str) };
unsafe { crate::typedarray_props::typed_array_has_property(ta, key_str) };
return if present { nanbox_true } else { nanbox_false };
}
if key_val.is_int32() {
Expand Down Expand Up @@ -2710,6 +2714,18 @@ pub extern "C" fn js_object_get_field_by_name(
}
}
}
// A user patch on the per-kind prototype
// (`Object.defineProperty(TA.prototype,
// "constructor", { get })` or a data overwrite)
// shadows the intrinsic — run the getter with
// `this` = the view (observable; test262
// speciesctor-get-ctor-inherited reads
// `result.constructor` and counts calls).
if let Some(v) =
crate::typedarray::species::prototype_constructor_patch(kind, addr)
{
return JSValue::from_bits(v.to_bits());
}
let name = crate::typedarray::name_for_kind(kind);
let ctor =
super::js_get_global_this_builtin_value(name.as_ptr(), name.len());
Expand All @@ -2735,8 +2751,21 @@ pub extern "C" fn js_object_get_field_by_name(
}
b"BYTES_PER_ELEMENT" => return JSValue::number(1.0),
b"constructor" => {
// An ArrayBuffer / SharedArrayBuffer cell answers
// with ITS constructor — only the Uint8Array
// (Buffer-backed view) representation reports
// `Uint8Array` (`ta.buffer.constructor ===
// ArrayBuffer`, test262 ctors/buffer-arg/
// typedarray-backed-by-sharedarraybuffer).
let name: &[u8] = if crate::buffer::is_shared_array_buffer(addr) {
b"SharedArrayBuffer"
} else if crate::buffer::is_any_array_buffer(addr) {
b"ArrayBuffer"
} else {
b"Uint8Array"
};
let ctor =
super::js_get_global_this_builtin_value(b"Uint8Array".as_ptr(), 10);
super::js_get_global_this_builtin_value(name.as_ptr(), name.len());
return JSValue::from_bits(ctor.to_bits());
}
_ => {}
Expand Down Expand Up @@ -3589,6 +3618,22 @@ pub extern "C" fn js_object_get_field_by_name(
let ctor = super::js_get_global_this_builtin_value(b"DataView".as_ptr(), 8);
return JSValue::from_bits(ctor.to_bits());
}
// An ArrayBuffer / SharedArrayBuffer answers with ITS
// constructor (`ta.buffer.constructor === ArrayBuffer`,
// test262 ctors/buffer-arg/typedarray-backed-by-
// sharedarraybuffer).
if crate::buffer::is_shared_array_buffer(obj as usize) {
let ctor = super::js_get_global_this_builtin_value(
b"SharedArrayBuffer".as_ptr(),
17,
);
return JSValue::from_bits(ctor.to_bits());
}
if crate::buffer::is_any_array_buffer(obj as usize) {
let ctor =
super::js_get_global_this_builtin_value(b"ArrayBuffer".as_ptr(), 11);
return JSValue::from_bits(ctor.to_bits());
}
if crate::buffer::is_uint8array_buffer(obj as usize) {
let ctor =
super::js_get_global_this_builtin_value(b"Uint8Array".as_ptr(), 10);
Expand Down
63 changes: 55 additions & 8 deletions crates/perry-runtime/src/object/global_this.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5401,17 +5401,64 @@ extern "C" fn typed_array_from_thunk(
if kind_opt.is_none() {
require_typed_array_from_of_constructor();
}
// Past the constructor check, the source is read — its `@@iterator` invoked,
// or its `length` getter + indexed elements evaluated — and any throwing
// user iterator/getter propagates. The map callback is validated up front by
// `js_array_from_mapped`.
// Spec order: validate the map callback BEFORE the source is read.
let mapped = map_fn.to_bits() != crate::value::TAG_UNDEFINED;
let arr = if mapped {
crate::array::js_array_from_mapped(source, map_fn, this_arg)
let map_closure = if mapped {
crate::array::js_validate_array_callback(map_fn) as *const crate::closure::ClosureHeader
} else {
crate::array::js_array_from_value(source)
std::ptr::null()
};
typed_array_create_from_values(kind_opt, arr)
// Read the source's RAW kValues — its `@@iterator` invoked, or its
// `ToLength(length)` + indexed elements evaluated — any throwing user
// iterator/getter propagates (test262 from/arylk-*-error).
let raw = unsafe { crate::typedarray::typed_array_from_source_raw_values(source) };
// Per-element `mappedValue = Call(mapfn, T, «kValue, k»)` then
// `Set(target, k, mappedValue)` — the map call and the (observable,
// possibly throwing) element coercion INTERLEAVE per spec, so an abrupt
// coercion at element k means the map callback never ran for k+1
// (test262 from/set-value-abrupt-completion).
let map_at = |k: usize, v: f64| -> f64 {
if map_closure.is_null() {
return v;
}
let prev = crate::object::js_implicit_this_set(this_arg);
let r = crate::closure::js_closure_call2(map_closure, v, k as f64);
crate::object::js_implicit_this_set(prev);
r
};
if let Some(kind) = kind_opt {
let out = crate::typedarray::typed_array_alloc(kind, raw.len() as u32);
for (k, &v) in raw.iter().enumerate() {
let m = map_at(k, v);
unsafe { crate::typedarray_props::species_result_store(out as usize, k, m) };
}
return crate::value::js_nanbox_pointer(out as i64);
}
// Custom `this` constructor: TypedArrayCreate(C, «len») then per-element
// [[Set]] (same interleave).
let len = raw.len();
let len_arg = [f64::from_bits(
crate::value::JSValue::number(len as f64).bits(),
)];
let ctor = crate::object::js_implicit_this_get();
let target = unsafe { super::js_new_function_construct(ctor, len_arg.as_ptr(), 1) };
let addr = crate::typedarray_props::typed_array_addr_from_value(target).unwrap_or_else(|| {
super::object_ops::throw_object_type_error(
b"TypedArray.from/of constructor did not return a TypedArray",
)
});
let ta_ptr = addr as *mut crate::typedarray::TypedArrayHeader;
let target_len = unsafe { crate::typedarray::js_typed_array_length(ta_ptr) } as usize;
if target_len < len {
super::object_ops::throw_object_type_error(
b"Derived TypedArray constructor created an array which was too small",
);
}
for (k, &v) in raw.iter().enumerate() {
let m = map_at(k, v);
unsafe { crate::typedarray_props::species_result_store(addr, k, m) };
}
target
}

/// `%TypedArray%.from`/`.of` step "If IsConstructor(`this`) is false, throw a
Expand Down
Loading
Loading