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/9885-perf-hooks-default-export-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Match Node's `node:perf_hooks` default-import keys while preserving namespace `default` and shared export identity.
1 change: 1 addition & 0 deletions crates/perry-dispatch/src/cjs_default_modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ cjs_default_namespace_modules!(
"path",
"path.posix",
"path.win32",
"perf_hooks",
"process",
"punycode",
"querystring",
Expand Down
33 changes: 33 additions & 0 deletions crates/perry-hir/src/lower/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,39 @@ export function perfHooksDefault() {
));
}

#[test]
fn native_perf_hooks_default_import_reads_cjs_namespace() {
let source = r#"
import hooks from "node:perf_hooks";
export function perfHooksDefaultImport() {
return hooks;
}
"#;
let module = perry_parser::parse_typescript(source, "perf-hooks-default-import.ts")
.expect("source parses");
let hir = super::lower_module(
&module,
"perf-hooks-default-import",
"perf-hooks-default-import.ts",
)
.expect("source lowers");
let function = hir
.functions
.iter()
.find(|function| function.name == "perfHooksDefaultImport")
.expect("exported function is lowered");

assert!(matches!(
function.body.as_slice(),
[Stmt::Return(Some(crate::ir::Expr::PropertyGet {
object,
property,
..
}))] if property == "default"
&& matches!(object.as_ref(), crate::ir::Expr::NativeModuleRef(module) if module == "perf_hooks")
));
}

#[test]
fn test_lower_type_param_scoping() {
let mut ctx = make_ctx();
Expand Down
14 changes: 4 additions & 10 deletions crates/perry-runtime/src/object/native_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,14 +702,6 @@ pub(crate) fn cjs_default_export_value(module_name: &str) -> Option<f64> {
"dgram".len(),
)),
"module" => Some(bound_native_callable_export_value("module", "Module")),
// node:perf_hooks has no distinct CJS shape — `module.exports` IS the
// namespace, and `default` is listed among its keys. Resolving to the
// same tag keeps `hooks.default.performance === hooks.performance`
// (the `performance` singleton resolves identically from either).
"perf_hooks" => Some(js_create_native_module_namespace(
b"perf_hooks".as_ptr(),
"perf_hooks".len(),
)),
"process" => Some(js_create_native_module_namespace(
b"process".as_ptr(),
"process".len(),
Expand Down Expand Up @@ -812,6 +804,7 @@ fn should_cache_native_module_namespace(module_name: &str) -> bool {
| "path.default"
| "path.posix.default"
| "path.win32.default"
| "perf_hooks.default"
| "punycode"
| "punycode.default"
| "punycode.ucs2"
Expand Down Expand Up @@ -936,12 +929,13 @@ unsafe fn native_module_property_by_name_impl(
// `typeof performance === "object"`, `performance.timeOrigin` (a
// constant), `performance.now` (a callable export), and
// `constants.NODE_PERFORMANCE_GC_*` (constants) all dispatch coherently.
if module_name == "perf_hooks" && property_name == "performance" {
if matches!(module_name, "perf_hooks" | "perf_hooks.default") && property_name == "performance"
{
// Singleton so `require("perf_hooks").performance` and the global
// `performance` are the same object (Node identity guarantee, #1327).
return crate::perf_hooks::performance_namespace();
}
if module_name == "perf_hooks" && property_name == "constants" {
if matches!(module_name, "perf_hooks" | "perf_hooks.default") && property_name == "constants" {
// Its OWN tag. Sharing the `perf_hooks` tag made every read of the
// constants object resolve against the MODULE's surface, so
// `Object.keys(constants)` enumerated the export list instead of the
Expand Down
17 changes: 17 additions & 0 deletions crates/perry-runtime/src/object/native_module/module_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,22 @@ const VM_MODULE_NAMESPACE_KEYS: &[&[u8]] = &[

const VM_CONSTANTS_KEYS: &[&[u8]] = &[b"USE_MAIN_CONTEXT_DEFAULT_LOADER", b"DONT_CONTEXTIFY"];

const PERF_HOOKS_DEFAULT_KEYS: &[&[u8]] = &[
b"Performance",
b"PerformanceEntry",
b"PerformanceMark",
b"PerformanceMeasure",
b"PerformanceObserver",
b"PerformanceObserverEntryList",
b"PerformanceResourceTiming",
b"monitorEventLoopDelay",
b"eventLoopUtilization",
b"timerify",
b"createHistogram",
b"performance",
b"constants",
];

// Linux-only open() flags: Node only enumerates these on platforms whose libc
// defines them (e.g. `O_DIRECT`/`O_NOATIME` are absent on macOS), so gate the
// enumerable-key tail by target so `Object.keys(constants)` matches Node here.
Expand Down Expand Up @@ -1862,6 +1878,7 @@ pub(crate) fn native_module_enumerable_keys(module_name: &str) -> Option<&'stati
b"constants",
b"default",
]),
"perf_hooks.default" => Some(PERF_HOOKS_DEFAULT_KEYS),
"perf_hooks.constants" => Some(&[
b"NODE_PERFORMANCE_GC_MAJOR",
b"NODE_PERFORMANCE_GC_MINOR",
Expand Down
1 change: 1 addition & 0 deletions crates/perry-runtime/src/object/native_module_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ mod cjs_default_dispatch_tests {
"path.default",
"path.posix.default",
"path.win32.default",
"perf_hooks.default",
"process.default",
"punycode.default",
"querystring.default",
Expand Down
Loading