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/10160-self-namespace-dynamic-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- `export * as Self from "./self"` now reads as the module's own namespace object through a dynamic `import()` namespace (and its destructuring); the entry was `undefined` because the populator loaded the module's namespace global before it was stored (#10160).
17 changes: 17 additions & 0 deletions crates/perry-codegen/src/codegen/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,23 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
blk.ret(DOUBLE, &value);
continue;
}
crate::NamespaceEntryKind::NestedNamespace { source_prefix }
if source_prefix == module_prefix =>
{
// #10160: `export * as Self from "./self"` reads this
// module's own namespace global at access time; the
// populator publishes the entry as a live accessor.
let wrapper = llmod.define_function(
&wrapper_name,
DOUBLE,
vec![(I64, "%this_closure".to_string())],
);
let _ = wrapper.create_block("entry");
let blk = wrapper.block_mut(0).unwrap();
let value = blk.load(DOUBLE, &format!("@__perry_ns_{module_prefix}"));
blk.ret(DOUBLE, &value);
continue;
}
crate::NamespaceEntryKind::ForeignVar {
source_prefix,
source_local,
Expand Down
27 changes: 24 additions & 3 deletions crates/perry-codegen/src/codegen/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1428,10 +1428,21 @@ pub(super) fn emit_namespace_populator(
let len_slot = blk.gep(I32, &lens_buf, &[(I64, &idx_str)]);
blk.store(I32, &format!("{}", key_len), &len_slot);

let is_live_binding = matches!(
entry.kind,
NamespaceEntryKind::LocalVar { .. } | NamespaceEntryKind::ForeignVar { .. }
// #10160: `export * as Self from "./self"` — this module's own
// `@__perry_ns_<prefix>` is only stored after `js_create_namespace`
// returns below, so loading it here would freeze `undefined` into
// the entry. Publish it as a live accessor instead; its getter
// wrapper (emitted next to the LocalVar wrappers in artifacts.rs)
// loads the global at read time.
let is_self_namespace = matches!(
&entry.kind,
NamespaceEntryKind::NestedNamespace { source_prefix } if source_prefix == module_prefix
);
let is_live_binding = is_self_namespace
|| matches!(
entry.kind,
NamespaceEntryKind::LocalVar { .. } | NamespaceEntryKind::ForeignVar { .. }
);
let live_slot = blk.gep(I8, &live_buf, &[(I64, &idx_str)]);
blk.store(I8, if is_live_binding { "1" } else { "0" }, &live_slot);

Expand Down Expand Up @@ -1496,6 +1507,16 @@ pub(super) fn emit_namespace_populator(
);
crate::expr::nanbox_pointer_inline(blk, &handle)
}
NamespaceEntryKind::NestedNamespace { .. } if is_self_namespace => {
let wrapper = namespace_live_getter_wrapper_symbol(module_prefix, i);
let blk = ctx.block();
let handle = blk.call(
I64,
"js_closure_alloc_singleton",
&[(PTR, &format!("@{}", wrapper))],
);
crate::expr::nanbox_pointer_inline(blk, &handle)
}
NamespaceEntryKind::NestedNamespace { source_prefix } => ctx
.block()
.load(DOUBLE, &format!("@__perry_ns_{}", source_prefix)),
Expand Down
2 changes: 2 additions & 0 deletions crates/perry/tests/source_graph_export_regressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -954,3 +954,5 @@ fn mixed_type_and_value_specifier_import_keeps_runtime_edge() {
"dependency initialized\n42\n"
);
}
#[path = "source_graph_export_regressions/issue_10160.rs"]
mod issue_10160;
77 changes: 77 additions & 0 deletions crates/perry/tests/source_graph_export_regressions/issue_10160.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! `export * as Self from "./self"` must read as the module's own namespace
//! through a dynamic `import()` namespace, not as `undefined` (#10160).

use super::{compile_and_run, write};

fn store(dir: &std::path::Path) {
write(
dir,
"store.ts",
"export class Service {\n\
\x20 static use(f: (s: string) => string) { return f(\"store-ok\") }\n\
}\n\
export const node = \"node-layer\"\n\
export * as InstanceStore from \"./store\"\n",
);
}

#[test]
fn self_namespace_reexport_is_defined_on_a_dynamic_import_namespace() {
let dir = tempfile::tempdir().unwrap();
store(dir.path());
write(
dir.path(),
"main.ts",
"import { InstanceStore as Static } from \"./store\"\n\
console.log(typeof Static, typeof Static.Service, Static.Service.use((s) => s))\n\
const mod = await import(\"./store\")\n\
console.log(Object.keys(mod).sort().join(\",\"))\n\
const { InstanceStore } = mod\n\
console.log(typeof InstanceStore, typeof InstanceStore.Service, InstanceStore.Service.use((s) => s + \"!\"))\n\
console.log(InstanceStore.InstanceStore === InstanceStore, InstanceStore.node, Static.node)\n",
);
assert_eq!(
compile_and_run(dir.path(), "main.ts"),
"object function store-ok\n\
InstanceStore,Service,node\n\
object function store-ok!\n\
true node-layer node-layer\n"
);
}

#[test]
fn self_namespace_reexport_is_defined_on_a_static_star_import() {
let dir = tempfile::tempdir().unwrap();
store(dir.path());
write(
dir.path(),
"main.ts",
"import * as ns from \"./store\"\n\
console.log(typeof ns.InstanceStore, ns.InstanceStore.Service.use((s) => s), ns.InstanceStore.node)\n",
);
assert_eq!(
compile_and_run(dir.path(), "main.ts"),
"object store-ok node-layer\n"
);
}

#[test]
fn foreign_namespace_reexport_stays_intact_on_a_dynamic_import_namespace() {
let dir = tempfile::tempdir().unwrap();
write(dir.path(), "other.ts", "export const value = 42\n");
write(
dir.path(),
"barrel.ts",
"export * as Other from \"./other\"\nexport * as Barrel from \"./barrel\"\n",
);
write(
dir.path(),
"main.ts",
"const mod = await import(\"./barrel\")\n\
console.log(typeof mod.Other, mod.Other.value, typeof mod.Barrel, mod.Barrel.Other.value)\n",
);
assert_eq!(
compile_and_run(dir.path(), "main.ts"),
"object 42 object 42\n"
);
}
Loading