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/9887-fs-promises-namespace-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Reuse the canonical `fs/promises` and `stream/promises` namespace objects when their native-module references become values.
21 changes: 21 additions & 0 deletions crates/perry-codegen/src/expr/property_get/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,27 @@ fn fs_parent_promises_property_installs_before_resolution() {
);
}

#[test]
fn fs_promises_native_module_value_uses_submodule_singleton() {
let mut module = Module::new("fs_promises_native_module_value.ts");
module.init = vec![Stmt::Return(Some(Expr::NativeModuleRef(
"fs/promises".to_string(),
)))];

let ir = String::from_utf8(compile_module(&module, ir_opts(false, None)).unwrap())
.expect("LLVM IR should be UTF-8");
let install = ir
.find("call void @js_node_submod_install_fs_promises()")
.unwrap_or_else(|| panic!("fs/promises must emit its submodule installer:\n{ir}"));
let namespace = ir
.find("call double @js_node_submodule_namespace")
.unwrap_or_else(|| panic!("fs/promises must use its submodule singleton:\n{ir}"));
assert!(
install < namespace,
"fs/promises installation must precede namespace creation:\n{ir}"
);
}

/// #7753, paired with `pic_cache_words_match_codegen` in
/// `perry-runtime/src/object/field_get_set/ic_miss.rs`.
///
Expand Down
16 changes: 16 additions & 0 deletions crates/perry-codegen/src/expr/static_field_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,22 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
// require("node:fs"); fs.constants` (call-result shape, fallback
// path here) both produce a real namespace object.
Expr::NativeModuleRef(name) => {
if let Some(submod_key) = crate::nm_install::native_namespace_submodule_key(name) {
let submod_idx = ctx.strings.intern(submod_key);
let submod_bytes_global =
format!("@{}", ctx.strings.entry(submod_idx).bytes_global);
let submod_len = submod_key.len().to_string();
let install_sym = crate::nm_install::nm_submod_install_symbol(submod_key);
let blk = ctx.block();
if let Some(symbol) = install_sym {
blk.call_void(symbol, &[]);
}
return Ok(blk.call(
DOUBLE,
"js_node_submodule_namespace",
&[(PTR, &submod_bytes_global), (I32, &submod_len)],
));
}
let mod_idx = ctx.strings.intern(name);
let mod_bytes_global = format!("@{}", ctx.strings.entry(mod_idx).bytes_global);
let mod_len_str = name.len().to_string();
Expand Down
7 changes: 1 addition & 6 deletions crates/perry-codegen/src/lower_call/native/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,12 +480,7 @@ pub(crate) fn lower_native_method_call(
// TAG_UNDEFINED sentinel below and `promises.realpath(p)` resolved
// `undefined` (the compiled CLI's file cache then normalized every path to
// `undefined` and each later fs call threw).
let normalized_module = module.strip_prefix("node:").unwrap_or(module);
let promises_submod_key = match normalized_module {
"fs/promises" => Some("fs_promises"),
"stream/promises" => Some("stream_promises"),
_ => None,
};
let promises_submod_key = crate::nm_install::native_namespace_submodule_key(module);
if let Some(submod_key) = promises_submod_key {
let submod_label = crate::expr::emit_string_literal_global(ctx, submod_key);
let install_sym = crate::nm_install::nm_submod_install_symbol(submod_key);
Expand Down
10 changes: 10 additions & 0 deletions crates/perry-codegen/src/nm_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,16 @@ pub(crate) fn nm_submod_install_symbol(key: &str) -> Option<&'static str> {
}
}

/// Native-module spellings whose value is implemented by the node-submodule
/// registry rather than a generic native-module namespace object.
pub(crate) fn native_namespace_submodule_key(name: &str) -> Option<&'static str> {
match name.strip_prefix("node:").unwrap_or(name) {
"fs/promises" => Some("fs_promises"),
"stream/promises" => Some("stream_promises"),
_ => None,
}
}

#[allow(dead_code)] // consumed only by codegen configurations that emit dispatch declarations
pub(crate) const NM_SUBMOD_INSTALL_SYMBOLS: &[&str] = &[
"js_node_submod_install_vm",
Expand Down
Loading