Skip to content
Closed
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
5 changes: 5 additions & 0 deletions changelog.d/9905-cluster-default-prototype.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### Fixed

- The `node:cluster` default export now inherits from the canonical
`EventEmitter.prototype`, so reflective prototype checks agree with Node while
preserving the cached singleton used by cluster event methods.
46 changes: 43 additions & 3 deletions crates/perry-runtime/src/cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,51 @@ thread_local! {
/// `import cluster from "node:cluster"`. Cached (see
/// `should_cache_native_module_namespace`), so EventEmitter methods can return
/// it for `cluster.on(...) === cluster` chaining.
fn cluster_default_value() -> f64 {
crate::object::js_create_native_module_namespace(
pub(crate) fn cluster_default_value() -> f64 {
let scope = crate::gc::RuntimeHandleScope::new();
let cluster = scope.root_nanbox_f64(crate::object::js_create_native_module_namespace(
b"cluster.default".as_ptr(),
"cluster.default".len(),
)
));
let event_emitter = scope.root_nanbox_f64(crate::object::bound_native_callable_export_value(
"events",
"EventEmitter",
));
if let Some(event_emitter_proto) =
crate::object::ordinary_function_prototype_value_for_read(event_emitter.get_nanbox_f64())
{
let event_emitter_proto = scope.root_nanbox_f64(event_emitter_proto);
let cluster_addr = (cluster.get_nanbox_u64() & crate::value::POINTER_MASK) as usize;
let proto_bits = event_emitter_proto.get_nanbox_u64();
if crate::object::prototype_chain::object_static_prototype(cluster_addr) != Some(proto_bits)
{
crate::object::prototype_chain::object_set_static_prototype(cluster_addr, proto_bits);
}
}
cluster.get_nanbox_f64()
}

#[cfg(test)]
mod cluster_default_prototype_tests {
use super::*;

#[test]
fn default_export_inherits_from_event_emitter() {
let scope = crate::gc::RuntimeHandleScope::new();
let cluster = scope.root_nanbox_f64(cluster_default_value());
let event_emitter = scope.root_nanbox_f64(
crate::object::bound_native_callable_export_value("events", "EventEmitter"),
);
let expected = crate::object::ordinary_function_prototype_value_for_read(
event_emitter.get_nanbox_f64(),
)
.expect("EventEmitter must expose a prototype object");

assert_eq!(
crate::object::js_object_get_prototype_of(cluster.get_nanbox_f64()).to_bits(),
expected.to_bits(),
);
}
}

fn cluster_emitter_event_name(event: f64) -> Option<String> {
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-runtime/src/object/native_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ pub(crate) fn cjs_default_export_value(module_name: &str) -> Option<f64> {
// #3687: `node:cluster` default import is a distinct EventEmitter-shaped
// `cluster.default` namespace (its `on`/`emit`/… reads diverge from the
// bare `import * as` namespace).
"cluster" => create_cjs_default_namespace("cluster"),
"cluster" => Some(crate::cluster::cluster_default_value()),
// #3693: `node:dgram` default === the module namespace (CJS
// `module.exports`); a cached singleton makes `dgram === ns.default`.
"dgram" => Some(js_create_native_module_namespace(
Expand Down
Loading