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
22 changes: 22 additions & 0 deletions crates/perry-runtime/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,28 @@ pub extern "C" fn js_date_value_of(timestamp: f64) -> f64 {
if let Some((_, payload)) = crate::builtins::boxed_primitive_payload(timestamp) {
return payload;
}
// A real Date cell yields its numeric timestamp.
if is_date_value(timestamp) {
return date_cell_timestamp(timestamp);
}
// Any other ordinary object: `obj.valueOf()` follows ordinary member
// dispatch, so a user-defined own `valueOf` wins over the default
// Object.prototype.valueOf (which returns the receiver). `Object(x)`
// returns `x` unchanged, so `Object(x).valueOf()` must run x's own
// `valueOf` (test262 built-ins/Object/S9.9_A6). Route through the shared
// `valueOf` dispatch in `js_native_call_method`, which performs the own-
// property lookup and falls back to the default for plain objects.
if crate::value::JSValue::from_bits(timestamp.to_bits()).is_pointer() {
return unsafe {
crate::object::js_native_call_method(
timestamp,
b"valueOf".as_ptr() as *const i8,
"valueOf".len(),
std::ptr::null(),
0,
)
};
}

date_cell_timestamp(timestamp)
}
Expand Down
17 changes: 17 additions & 0 deletions crates/perry-runtime/src/object/native_call_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4681,6 +4681,23 @@ pub unsafe extern "C" fn js_native_call_method(
// while ordinary objects now get the inherited default instead of
// falling through to "valueOf is not a function".
"valueOf" => {
// A user-defined own `valueOf` wins over the default, mirroring the
// `toLocaleString` arm below. `Object(x)` returns `x` unchanged, so
// `Object(x).valueOf()` must run x's own `valueOf`
// (test262 built-ins/Object/S9.9_A6). The explicit-base form
// `Object.prototype.valueOf.call(x)` goes through
// `object_prototype_value_of_thunk` instead and correctly skips this
// own-property lookup.
let own =
crate::object::js_object_get_own_field_or_undef(object, b"valueOf".as_ptr(), 7);
if let Some(result) = call_primitive_closure_value(
object,
JSValue::from_bits(own.to_bits()),
args_ptr,
args_len,
) {
return result;
}
return js_object_default_value_of(object);
}

Expand Down
26 changes: 26 additions & 0 deletions test-parity/node-suite/object/valueof-tolocalestring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,29 @@ logTypeError("call toLocaleString null-prototype", () =>
);
logCall("call valueOf null", () => Object.prototype.valueOf.call(null));
logCall("call toLocaleString undefined", () => Object.prototype.toLocaleString.call(undefined));

// #3986: a user-defined own `valueOf` wins over the default Object.prototype
// .valueOf during ordinary member dispatch. `Object(x)` returns `x` unchanged,
// so `Object(x).valueOf()` must run x's own `valueOf` (test262 S9.9_A6). The
// explicit-base form `Object.prototype.valueOf.call(x)` must NOT consult the
// own property (it returns the receiver) — guarding against infinite recursion.
function MyValue(this: any, v: number) {
this.value = v;
this.valueOf = function () {
return this.value;
};
}
const mv = new (MyValue as any)(1);
const boxed: any = Object(mv);
logCall("Object(x) identity", () => boxed === mv);
logCall("Object(x) own valueOf", () => boxed.valueOf());
logCall("Object(x) coercion", () => boxed + 4);
logCall("own valueOf via member", () => mv.valueOf());
logCall("own valueOf base-call identity", () => Object.prototype.valueOf.call(boxed) === mv);
const litValueOf = {
valueOf() {
return 42;
},
};
logCall("object literal own valueOf", () => litValueOf.valueOf());
logCall("object literal valueOf coercion", () => (litValueOf as any) * 2);
Loading