fix(hir): emit nested namespaces (#5130) - #5167
Conversation
A nested namespace was not emitted — accessing a member of an inner
namespace (G.Nested.value) yielded undefined and then threw, while
top-level namespaces worked.
Root cause: lower_namespace_as_class dropped nested TsModule items (the
ExportDecl/Stmt match arms fell through to _ => {}), and the dotted
TsNamespaceDecl body returned an empty class.
Fix: lower nested namespaces recursively as their own synthetic class
registered under a qualified Outer.Inner name, and give the outer
namespace a static field Inner holding a ClassRef to it. Outer.Inner then
resolves to the inner namespace (registered as a static field so
has_static_field routes it to StaticFieldGet) and Outer.Inner.member reads
its statics — a runtime property/method access on a class-ref already
resolves static fields and methods. Works to any nesting depth, across
cross-level references, and when a nested namespace is aliased to a value.
📝 WalkthroughWalkthroughAdds support for nested TypeScript ChangesNested namespace lowering and regression tests
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
crates/perry/tests/issue_5130_nested_namespace.rs (1)
58-106: ⚡ Quick winAdd a regression for dotted namespace syntax (
namespace A.B {}) to close the remaining gap.Current tests validate block-nested namespaces, but not dotted declarations. Given
TsNamespaceDeclstill has a separate lowering path incrates/perry-hir/src/lower/module_decl.rs(Line 2001), a dedicated test here would lock expected behavior (either supported output or explicit unsupported assertion).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/perry/tests/issue_5130_nested_namespace.rs` around lines 58 - 106, Add a new test function in the same file following the pattern of nested_namespace_members_resolve and deeply_nested_namespaces_and_cross_level_refs to cover dotted namespace syntax (e.g., namespace A.B.C {}). The test should use compile_and_run to verify either the expected output behavior when using dotted declarations or provide an explicit assertion showing the feature is unsupported. This regression test will lock in the expected behavior for the dotted namespace syntax path referenced in the TsNamespaceDecl lowering logic, complementing the block-nested namespace tests already present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/perry-hir/src/lower/module_decl.rs`:
- Around line 2284-2291: The exported nested namespace handling path (around
line 2284 where lower_nested_namespace is called) is missing a guard for the
declare modifier that exists in the non-exported arm at line 2152. Add a check
to skip the lower_nested_namespace call when ts_module.declare is true, ensuring
that export declare namespace declarations are not lowered to runtime artifacts.
This guard should match the logic in the non-exported arm to prevent type-only
namespace declarations from generating class or static field emissions.
---
Nitpick comments:
In `@crates/perry/tests/issue_5130_nested_namespace.rs`:
- Around line 58-106: Add a new test function in the same file following the
pattern of nested_namespace_members_resolve and
deeply_nested_namespaces_and_cross_level_refs to cover dotted namespace syntax
(e.g., namespace A.B.C {}). The test should use compile_and_run to verify either
the expected output behavior when using dotted declarations or provide an
explicit assertion showing the feature is unsupported. This regression test will
lock in the expected behavior for the dotted namespace syntax path referenced in
the TsNamespaceDecl lowering logic, complementing the block-nested namespace
tests already present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 26499f83-f148-4093-9c28-f9cff874e2ca
📒 Files selected for processing (2)
crates/perry-hir/src/lower/module_decl.rscrates/perry/tests/issue_5130_nested_namespace.rs
| ast::Decl::TsModule(ts_module) => { | ||
| lower_nested_namespace( | ||
| ctx, | ||
| module, | ||
| ns_name, | ||
| ts_module, | ||
| &mut ns_static_fields, | ||
| )?; |
There was a problem hiding this comment.
Missing declare guard emits runtime namespace artifacts for type-only declarations.
At Line 2284, exported nested namespaces are always lowered. Unlike the non-exported arm (Line 2152), this path does not skip ts_module.declare, so export declare namespace ... can incorrectly produce runtime class/static-field emission.
Proposed fix
- ast::Decl::TsModule(ts_module) => {
- lower_nested_namespace(
- ctx,
- module,
- ns_name,
- ts_module,
- &mut ns_static_fields,
- )?;
- }
+ ast::Decl::TsModule(ts_module) => {
+ if !ts_module.declare {
+ lower_nested_namespace(
+ ctx,
+ module,
+ ns_name,
+ ts_module,
+ &mut ns_static_fields,
+ )?;
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ast::Decl::TsModule(ts_module) => { | |
| lower_nested_namespace( | |
| ctx, | |
| module, | |
| ns_name, | |
| ts_module, | |
| &mut ns_static_fields, | |
| )?; | |
| ast::Decl::TsModule(ts_module) => { | |
| if !ts_module.declare { | |
| lower_nested_namespace( | |
| ctx, | |
| module, | |
| ns_name, | |
| ts_module, | |
| &mut ns_static_fields, | |
| )?; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/perry-hir/src/lower/module_decl.rs` around lines 2284 - 2291, The
exported nested namespace handling path (around line 2284 where
lower_nested_namespace is called) is missing a guard for the declare modifier
that exists in the non-exported arm at line 2152. Add a check to skip the
lower_nested_namespace call when ts_module.declare is true, ensuring that export
declare namespace declarations are not lowered to runtime artifacts. This guard
should match the logic in the non-exported arm to prevent type-only namespace
declarations from generating class or static field emissions.
Fixes #5130.
Problem
A nested
namespacewas not emitted — accessing a member of an inner namespace yieldedundefinedand then threw. Top-level namespaces worked.Root cause
lower_namespace_as_classdropped nestedTsModuleitems — theExportDeclandStmtmatch arms fell through to_ => {}— and the dottedTsNamespaceDeclbody returned an empty class.Fix
Nested namespaces are now lowered recursively as their own synthetic class registered under a qualified
Outer.Innername, and the outer namespace gains a static fieldInnerholding aClassRefto it:Outer.Innerresolves viahas_static_field→StaticFieldGet(returning theClassRef).Outer.Inner.memberis then a runtime property/method access on a class-ref, which already resolves the inner class's static fields and methods.Handles arbitrary nesting depth, cross-level references (an inner function reading an enclosing namespace's members), and aliasing a nested namespace to a value (
const M = Outer.Mid). Both exported and non-exported nested namespaces are routed the same way.Verification
Two new integration tests in
crates/perry/tests/issue_5130_nested_namespace.rs(green).perry-hirsuite green.No changelog/version bump per maintainer's release-at-merge workflow.
Summary by CodeRabbit
Outer.Inner.member).