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
3 changes: 3 additions & 0 deletions changelog.d/8877-release-r23-node-function-prototype.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Match Node's prototype surface for callback-style native module exports such as `fs.readFile`.
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ pub(crate) fn is_native_module_constructor_export(module: &str, property: &str)
"crypto.KeyObject" => property == "from",
"dns" | "dns/promises" => property == "getServers",
"events" => property == "once",
// Node's callback-style fs helpers are callable but not constructors.
// Keep this exact: class exports such as ReadStream/WriteStream must
// still materialize a real prototype for userland subclassing.
"fs" => property == "readFile",
"http" => property == "setMaxIdleHTTPParsers",
"inspector" => matches!(property, "close" | "url"),
"inspector.DOMStorage" => matches!(
Expand Down Expand Up @@ -199,8 +195,8 @@ mod tests {
use super::is_native_module_constructor_export;

#[test]
fn fs_read_file_is_not_a_constructor_but_stream_classes_are() {
assert!(!is_native_module_constructor_export("fs", "readFile"));
fn fs_read_file_and_stream_classes_expose_prototypes() {
assert!(is_native_module_constructor_export("fs", "readFile"));
assert!(is_native_module_constructor_export("fs", "ReadStream"));
assert!(is_native_module_constructor_export(
"node:fs",
Expand Down
14 changes: 7 additions & 7 deletions crates/perry/tests/issue_5268_native_ctor_prototype_undefined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
//! `Object.create(undefined)` / `Object.setPrototypeOf(x, undefined)` then hit
//! the spec TypeError. Fix: recognize constructor-cased bound-native exports
//! (leading uppercase, not flagged non-constructable) and let the
//! synthetic-class path materialize a stable `.prototype` object — while
//! keeping non-constructor exports (`fs.readFile`, …) at `prototype ===
//! undefined`, matching Node's built-in non-constructor functions.
//! synthetic-class path materialize a stable `.prototype` object. Node also
//! exposes a real prototype on callback-style JavaScript wrapper exports such
//! as `fs.readFile`, despite the lower-case export name.

use std::path::PathBuf;
use std::process::Command;
Expand Down Expand Up @@ -97,13 +97,13 @@ const prototype: any = { child() { return null; } };
Object.setPrototypeOf(prototype, (EventEmitter as any).prototype);
console.log("setproto:", true);

// Non-constructor native exports keep prototype === undefined (no spurious
// synthesis), matching Node's built-in non-constructor functions.
console.log("nonctor:", (fs as any).readFile.prototype === undefined);
// Node's callback-style JavaScript wrapper exports also expose a prototype.
const readFileProto = (fs as any).readFile.prototype;
console.log("readFile:", typeof readFileProto === "object" && readFileProto !== null);
"#,
);
assert_eq!(
stdout,
"rs: true\nws: true\nee: true\ncreate: true\nchain: true\nsetproto: true\nnonctor: true\n"
"rs: true\nws: true\nee: true\ncreate: true\nchain: true\nsetproto: true\nreadFile: true\n"
);
}
10 changes: 7 additions & 3 deletions test-files/test_issue_5268_native_ctor_prototype_undefined.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const prototype: any = { child() { return null; } };
Object.setPrototypeOf(prototype, (EventEmitter as any).prototype);
console.log("setPrototypeOf(obj, EventEmitter.prototype) ok:", true);

// 4. Non-constructor native exports keep `prototype === undefined`, matching
// Node's built-in non-constructor functions (no spurious synthesis).
console.log("fs.readFile.prototype is undefined:", (fs as any).readFile.prototype === undefined);
// 4. Node also exposes a real prototype on callback-style JavaScript wrapper
// exports such as fs.readFile, despite the lower-case export name.
const readFileProto = (fs as any).readFile.prototype;
console.log(
"fs.readFile.prototype is object:",
typeof readFileProto === "object" && readFileProto !== null,
);
Loading