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: 18 additions & 4 deletions crates/perry-runtime/src/object/global_this.rs
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@ extern "C" fn global_this_queue_microtask_thunk(
/// Tag detection uses the same coarse NaN-box / GC-type discrimination
/// the rest of the runtime relies on: arrays → `"[object Array]"`,
/// strings → `"[object String]"`, null/undefined → matching tags,
/// numbers/bools → primitive tags, generic objects/closures
/// numbers/bools/functions → primitive/builtin tags, generic objects →
/// `"[object Object]"`.
///
/// Unblocks ramda's `_isArguments.js` IIFE which evaluates
Expand Down Expand Up @@ -1780,6 +1780,11 @@ fn set_intrinsic_to_string_tag(obj: *mut ObjectHeader, tag: &str) {
f64::from_bits(crate::js_nanbox_string(tag_str as i64).to_bits()),
);
}
crate::symbol::set_symbol_property_attrs(
obj as usize,
sym as usize,
super::PropertyAttrs::new(false, false, true),
);
}

/// Build a `TypeError` value for a `%Generator.prototype%` method invoked on a
Expand Down Expand Up @@ -2545,9 +2550,18 @@ pub(crate) fn populate_global_this_builtins(singleton: *mut ObjectHeader) {
// gated on the AST shape and never read these fields. Math uses the
// richer install that also exposes per-method name/length descriptors.
match name {
"Math" => install_math_namespace(ns_obj),
"JSON" => install_json_namespace_members(ns_obj),
"Reflect" => install_reflect_namespace_members(ns_obj),
"Math" => {
install_math_namespace(ns_obj);
set_intrinsic_to_string_tag(ns_obj, "Math");
}
"JSON" => {
install_json_namespace_members(ns_obj);
set_intrinsic_to_string_tag(ns_obj, "JSON");
}
"Reflect" => {
install_reflect_namespace_members(ns_obj);
set_intrinsic_to_string_tag(ns_obj, "Reflect");
}
"Atomics" => install_atomics_namespace_members(ns_obj),
"Intl" => crate::intl::install_intl_namespace(ns_obj),
_ => {}
Expand Down
15 changes: 15 additions & 0 deletions crates/perry-runtime/src/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,21 @@ pub unsafe extern "C" fn js_object_to_string(value: f64) -> f64 {
let str_ptr = crate::string::js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32);
return f64::from_bits(STRING_TAG | (str_ptr as u64 & POINTER_MASK));
}
if (raw_addr >= 0x10000 && crate::closure::is_closure_ptr(raw_addr))
|| crate::object::is_class_object_ptr(raw_addr as *const u8)
{
let bytes = b"[object Function]";
let str_ptr = crate::string::js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32);
return f64::from_bits(STRING_TAG | (str_ptr as u64 & POINTER_MASK));
}
if jsv.is_int32() {
let class_id = (bits & 0xFFFF_FFFF) as u32;
if crate::object::is_class_id_registered(class_id) {
let bytes = b"[object Function]";
let str_ptr = crate::string::js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32);
return f64::from_bits(STRING_TAG | (str_ptr as u64 & POINTER_MASK));
}
}
if jsv.is_int32() || jsv.is_number() {
let bytes = b"[object Number]";
let str_ptr = crate::string::js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32);
Expand Down
35 changes: 35 additions & 0 deletions test-parity/node-suite/globals/object-tostring-brands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function ordinary() {}
const arrow = () => {};
class Klass {}

for (const [label, value] of [
["ordinary", ordinary],
["arrow", arrow],
["class", Klass],
["Array", Array],
["Math.max", Math.max],
["Math", Math],
["JSON", JSON],
["Reflect", Reflect],
] as const) {
console.log(label + ":", Object.prototype.toString.call(value));
}

for (const [label, value] of [
["Math", Math],
["JSON", JSON],
["Reflect", Reflect],
] as const) {
const desc = Object.getOwnPropertyDescriptor(value, Symbol.toStringTag);
const rendered = desc
? JSON.stringify([desc.value, desc.writable, desc.enumerable, desc.configurable])
: "missing";
console.log(label + " tag desc:", rendered);
}

ordinary[Symbol.toStringTag] = "CallableOverride";
console.log("function override:", Object.prototype.toString.call(ordinary));

const namespaceOverride = Object.create(Math);
Object.defineProperty(namespaceOverride, Symbol.toStringTag, { value: "NamespaceOverride" });
console.log("namespace override:", Object.prototype.toString.call(namespaceOverride));