Skip to content

fix(es/resolver): Merge re-opened TypeScript namespace scopes - #11872

Open
Onyeka Obi (MavenRain) wants to merge 12 commits into
swc-project:mainfrom
MavenRain:fix/resolver-namespace-merge
Open

fix(es/resolver): Merge re-opened TypeScript namespace scopes#11872
Onyeka Obi (MavenRain) wants to merge 12 commits into
swc-project:mainfrom
MavenRain:fix/resolver-namespace-merge

Conversation

@MavenRain

Copy link
Copy Markdown
Contributor

Description

Closes #11607. Unblocks #11514.

In TypeScript, re-declaring a namespace with the same name merges it;
identifiers in any declaration's body see bindings declared in earlier
declarations. SWC's resolver was creating a fresh block scope (with
Mark::fresh) for every namespace X { ... } body, leaving sibling
re-declarations with disjoint scopes. Lookups in the second body walked their
own (empty) scope, then fell through to the outer scope and resolved to
whatever happened to be declared there.

Repro (also added as tests/ts-resolver/namespace_reopen)

  namespace Test {
      export const a = 1;
  }
  const a = "out";
  namespace Test {
      export const b = a + 1;  // `a` should be Test.a, not outer
  }

Before: a resolved to outer const a (a__2).
After: a resolves to Test.a from the first body (a__3).

Fix

A Resolver now carries a Rc<RefCell<FxHashMap<(Mark, Atom), NamespaceBody>>>
cache keyed by (parent_scope.mark, namespace_name). On
visit_mut_ts_module_decl:

  1. If a cache entry exists for this key, the child scope adopts the cached
    mark, declared symbols, and declared types before visiting the body. All
    earlier-declaration bindings are visible to lookups, and the merged scope's
    mark is preserved so identifiers across declarations share a SyntaxContext.
  2. After visiting, the child writes its accumulated bindings back to the
    cache, so a third (or fourth) re-declaration sees the union.

Cache keying on the parent scope's mark makes nested namespace re-opens behave
correctly: namespace Outer { namespace Inner {} } namespace Outer { namespace
Inner {} } merges the outer pair, then the inner pair (whose parent mark is
now the outer's merged mark) also merges.

Test coverage

The new fixture under tests/ts-resolver/namespace_reopen exercises the issue's
exact repro. The full swc_ecma_transforms_base test suite (~7500 tests,
including the 5182-fixture ts_resolver corpus) passes unchanged.

@MavenRain
Onyeka Obi (MavenRain) requested a review from a team as a code owner May 20, 2026 02:30
@changeset-bot

changeset-bot Bot commented May 20, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7ab166b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@CLAassistant

CLAassistant commented May 20, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b94ef6823a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment thread crates/swc_ecma_transforms_base/src/resolver/mod.rs Outdated
@codspeed-hq

codspeed-hq Bot commented May 20, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 200 untouched benchmarks
⏩ 61 skipped benchmarks1


Comparing MavenRain:fix/resolver-namespace-merge (7ab166b) with main (7e14950)2

Open in CodSpeed

Footnotes

  1. 61 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (5bf27fd) during the generation of this report, so 7e14950 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Comment thread crates/swc_ecma_transforms_base/src/resolver/mod.rs Outdated
@MavenRain
Onyeka Obi (MavenRain) force-pushed the fix/resolver-namespace-merge branch from b94ef68 to cee5448 Compare May 20, 2026 22:22
@MavenRain

Copy link
Copy Markdown
Contributor Author

I updated 18 fixture files (16 crates/swc/tests/tsc-references/* files, plus swc_ecma_transforms_typescript's namespace_004 snapshot and namespace-and-enum/output.js) to reflect the merged-namespace resolver
output. All eight CI-failing fixtures exercise re-opened module/namespace blocks; after the resolver fix the downstream TypeScript transform correctly recognizes them as the same binding and emits (N.E) instead of the defensive (N.E || (N.E = {})) on re-opens after the first. Runtime behavior is unchanged (|| short-circuits to the already-created object in the old emission).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cee5448477

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment thread crates/swc_ecma_transforms_base/src/resolver/mod.rs Outdated
Comment thread crates/swc_ecma_transforms_base/src/resolver/mod.rs Outdated
Onyeka Obi (MavenRain) added a commit to MavenRain/swc that referenced this pull request May 21, 2026
  Address review feedback from @magic-akari / Codex on swc-project#11872: only
  `export …` declarations should be shared across sibling re-opens of a
  TypeScript namespace, not every declaration.  Each re-open now gets a
  fresh per-body scope for its private bindings; only exported names
  live in the cache-backed `NamespaceExportScope`, which the body scope
  exposes via a `shared` link so reference lookups still see
  sibling-declared exports with a stable `SyntaxContext`.

  A per-body pre-scan of `export` declarations drives the routing in
  `modify`, making the decision order-independent: TypeScript's
  var/function same-body merge still holds
  (`export var a = 1; for (var a; …)`) while a non-exported re-open of a
  sibling-exported name stays isolated — fixing the `namespace_004`
  `MyEnum.A` `1`→`2` regression Codex flagged.

  Added tests/ts-resolver/namespace_reopen_private covering @magic-akari's
  example; updated fixture snapshots accordingly (re-opened non-exported
  classes / enums no longer trigger the "defined multiple times"
  diagnostic).

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
@MavenRain
Onyeka Obi (MavenRain) force-pushed the fix/resolver-namespace-merge branch from cee5448 to d9ffafb Compare May 21, 2026 13:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9ffafbeba

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment thread crates/swc_ecma_transforms_base/src/resolver/mod.rs Outdated
Onyeka Obi (MavenRain) added a commit to MavenRain/swc that referenced this pull request May 21, 2026
…pens

    Address Codex P1 on swc-project#11872: the cache key for `TsModuleDecl` previously
    used `self.namespace_export.mark` whenever it was present, so a nested
    namespace declared inside an outer namespace body was always keyed to
    the outer's stable export mark.  That made the two `Inner` declarations
    in

        namespace Outer { namespace Inner { export const a = 1 } }
        namespace Outer { namespace Inner { export const b = a } }

    collide on a single cache entry and merge, even though TypeScript keeps
    non-exported members local to each declaration body.

    The cache-parent mark now falls back to `self.current.mark` (the outer
    re-open's per-body mark) unless the nested namespace name is in the
    enclosing body's pre-scanned `namespace_export_names`.  Two outer
    re-opens hold distinct body marks, so their non-exported `Inner`
    children land in disjoint cache entries and stay isolated; exported
    nested namespaces still key on the outer's stable export mark and
    merge across re-opens.

    Added tests/ts-resolver/namespace_reopen_nested_private exercising the
    Codex case (second `Inner`'s reference to `a` is unresolved,
    confirming isolation).

    nested namespaces still key on the outer's stable export mark and
    merge across re-opens.

    Added tests/ts-resolver/namespace_reopen_nested_private exercising the
    Codex case (second `Inner`'s reference to `a` is unresolved,
    confirming isolation).

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf4ec2a736

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment thread crates/swc_ecma_transforms_base/src/resolver/mod.rs Outdated
Comment thread crates/swc_ecma_transforms_base/src/resolver/mod.rs
Comment thread crates/swc_ecma_transforms_base/src/resolver/mod.rs Outdated
Onyeka Obi (MavenRain) added a commit to MavenRain/swc that referenced this pull request May 21, 2026
  Address review feedback from @magic-akari / Codex on swc-project#11872: only
  `export …` declarations should be shared across sibling re-opens of a
  TypeScript namespace, not every declaration.  Each re-open now gets a
  fresh per-body scope for its private bindings; only exported names
  live in the cache-backed `NamespaceExportScope`, which the body scope
  exposes via a `shared` link so reference lookups still see
  sibling-declared exports with a stable `SyntaxContext`.

  A per-body pre-scan of `export` declarations drives the routing in
  `modify`, making the decision order-independent: TypeScript's
  var/function same-body merge still holds
  (`export var a = 1; for (var a; …)`) while a non-exported re-open of a
  sibling-exported name stays isolated — fixing the `namespace_004`
  `MyEnum.A` `1`→`2` regression Codex flagged.

  Added tests/ts-resolver/namespace_reopen_private covering @magic-akari's
  example; updated fixture snapshots accordingly (re-opened non-exported
  classes / enums no longer trigger the "defined multiple times"
  diagnostic).

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Onyeka Obi (MavenRain) added a commit to MavenRain/swc that referenced this pull request May 21, 2026
…pens

    Address Codex P1 on swc-project#11872: the cache key for `TsModuleDecl` previously
    used `self.namespace_export.mark` whenever it was present, so a nested
    namespace declared inside an outer namespace body was always keyed to
    the outer's stable export mark.  That made the two `Inner` declarations
    in

        namespace Outer { namespace Inner { export const a = 1 } }
        namespace Outer { namespace Inner { export const b = a } }

    collide on a single cache entry and merge, even though TypeScript keeps
    non-exported members local to each declaration body.

    The cache-parent mark now falls back to `self.current.mark` (the outer
    re-open's per-body mark) unless the nested namespace name is in the
    enclosing body's pre-scanned `namespace_export_names`.  Two outer
    re-opens hold distinct body marks, so their non-exported `Inner`
    children land in disjoint cache entries and stay isolated; exported
    nested namespaces still key on the outer's stable export mark and
    merge across re-opens.

    Added tests/ts-resolver/namespace_reopen_nested_private exercising the
    Codex case (second `Inner`'s reference to `a` is unresolved,
    confirming isolation).

    nested namespaces still key on the outer's stable export mark and
    merge across re-opens.

    Added tests/ts-resolver/namespace_reopen_nested_private exercising the
    Codex case (second `Inner`'s reference to `a` is unresolved,
    confirming isolation).

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
@MavenRain
Onyeka Obi (MavenRain) force-pushed the fix/resolver-namespace-merge branch from bf4ec2a to 450fd85 Compare May 21, 2026 20:55

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 450fd85e81

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment thread crates/swc_ecma_transforms_base/src/resolver/mod.rs Outdated
@MavenRain
Onyeka Obi (MavenRain) force-pushed the fix/resolver-namespace-merge branch from 78b7d08 to a9c8700 Compare May 22, 2026 16:00
Onyeka Obi (MavenRain) added a commit to MavenRain/swc that referenced this pull request May 22, 2026
  Address review feedback from @magic-akari / Codex on swc-project#11872: only
  `export …` declarations should be shared across sibling re-opens of a
  TypeScript namespace, not every declaration.  Each re-open now gets a
  fresh per-body scope for its private bindings; only exported names
  live in the cache-backed `NamespaceExportScope`, which the body scope
  exposes via a `shared` link so reference lookups still see
  sibling-declared exports with a stable `SyntaxContext`.

  A per-body pre-scan of `export` declarations drives the routing in
  `modify`, making the decision order-independent: TypeScript's
  var/function same-body merge still holds
  (`export var a = 1; for (var a; …)`) while a non-exported re-open of a
  sibling-exported name stays isolated — fixing the `namespace_004`
  `MyEnum.A` `1`→`2` regression Codex flagged.

  Added tests/ts-resolver/namespace_reopen_private covering @magic-akari's
  example; updated fixture snapshots accordingly (re-opened non-exported
  classes / enums no longer trigger the "defined multiple times"
  diagnostic).

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Onyeka Obi (MavenRain) added a commit to MavenRain/swc that referenced this pull request May 22, 2026
…pens

    Address Codex P1 on swc-project#11872: the cache key for `TsModuleDecl` previously
    used `self.namespace_export.mark` whenever it was present, so a nested
    namespace declared inside an outer namespace body was always keyed to
    the outer's stable export mark.  That made the two `Inner` declarations
    in

        namespace Outer { namespace Inner { export const a = 1 } }
        namespace Outer { namespace Inner { export const b = a } }

    collide on a single cache entry and merge, even though TypeScript keeps
    non-exported members local to each declaration body.

    The cache-parent mark now falls back to `self.current.mark` (the outer
    re-open's per-body mark) unless the nested namespace name is in the
    enclosing body's pre-scanned `namespace_export_names`.  Two outer
    re-opens hold distinct body marks, so their non-exported `Inner`
    children land in disjoint cache entries and stay isolated; exported
    nested namespaces still key on the outer's stable export mark and
    merge across re-opens.

    Added tests/ts-resolver/namespace_reopen_nested_private exercising the
    Codex case (second `Inner`'s reference to `a` is unresolved,
    confirming isolation).

    nested namespaces still key on the outer's stable export mark and
    merge across re-opens.

    Added tests/ts-resolver/namespace_reopen_nested_private exercising the
    Codex case (second `Inner`'s reference to `a` is unresolved,
    confirming isolation).

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…wc-project#11607)

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…pe resolver output

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
  Address review feedback from @magic-akari / Codex on swc-project#11872: only
  `export …` declarations should be shared across sibling re-opens of a
  TypeScript namespace, not every declaration.  Each re-open now gets a
  fresh per-body scope for its private bindings; only exported names
  live in the cache-backed `NamespaceExportScope`, which the body scope
  exposes via a `shared` link so reference lookups still see
  sibling-declared exports with a stable `SyntaxContext`.

  A per-body pre-scan of `export` declarations drives the routing in
  `modify`, making the decision order-independent: TypeScript's
  var/function same-body merge still holds
  (`export var a = 1; for (var a; …)`) while a non-exported re-open of a
  sibling-exported name stays isolated — fixing the `namespace_004`
  `MyEnum.A` `1`→`2` regression Codex flagged.

  Added tests/ts-resolver/namespace_reopen_private covering @magic-akari's
  example; updated fixture snapshots accordingly (re-opened non-exported
  classes / enums no longer trigger the "defined multiple times"
  diagnostic).

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…pens

    Address Codex P1 on swc-project#11872: the cache key for `TsModuleDecl` previously
    used `self.namespace_export.mark` whenever it was present, so a nested
    namespace declared inside an outer namespace body was always keyed to
    the outer's stable export mark.  That made the two `Inner` declarations
    in

        namespace Outer { namespace Inner { export const a = 1 } }
        namespace Outer { namespace Inner { export const b = a } }

    collide on a single cache entry and merge, even though TypeScript keeps
    non-exported members local to each declaration body.

    The cache-parent mark now falls back to `self.current.mark` (the outer
    re-open's per-body mark) unless the nested namespace name is in the
    enclosing body's pre-scanned `namespace_export_names`.  Two outer
    re-opens hold distinct body marks, so their non-exported `Inner`
    children land in disjoint cache entries and stay isolated; exported
    nested namespaces still key on the outer's stable export mark and
    merge across re-opens.

    Added tests/ts-resolver/namespace_reopen_nested_private exercising the
    Codex case (second `Inner`'s reference to `a` is unresolved,
    confirming isolation).

    nested namespaces still key on the outer's stable export mark and
    merge across re-opens.

    Added tests/ts-resolver/namespace_reopen_nested_private exercising the
    Codex case (second `Inner`'s reference to `a` is unresolved,
    confirming isolation).

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
  `pre_scan_namespace_exports` only inspected `ModuleDecl::ExportDecl`, so
  `export import A = ...` (a `TsImportEqualsDecl { is_export: true, .. }`)
  was never added to `namespace_export_names`. The alias binding was then
  routed to the body-local scope instead of the namespace's merged export
  scope, and references from sibling re-opens of the same namespace fell
  through to outer/unresolved bindings, violating TypeScript's
  namespace-merge semantics.

  Recognise the exported import-equals form in the pre-scan, adding the
  alias to `values` (and to `types` when not `is_type_only`), mirroring
  how `export namespace`/`export class`/`export enum` are tracked. The
  existing `visit_mut_ts_import_equals_decl` `modify` call now routes the
  binding to the shared export scope, and type-position lookups in
  sibling bodies resolve through `mark_for_ref_inner`'s
  `declared_types -> declared_symbols` fall-through.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
@MavenRain
Onyeka Obi (MavenRain) force-pushed the fix/resolver-namespace-merge branch from a9c8700 to 7aa055b Compare May 26, 2026 19:55
@kdy1

Copy link
Copy Markdown
Member

magic-akari Can you take a look?

@magic-akari

Copy link
Copy Markdown
Member

After reviewing the test snapshots and generated outputs, no issues have been found so far.

@magic-akari

Copy link
Copy Markdown
Member

Codex (@codex) review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7aa055b10c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

/// bindings, violating TypeScript's namespace-merge semantics.
fn pre_scan_namespace_exports(body: &TsNamespaceBody) -> NamespaceExportNames {
let mut scan = NamespaceExportNames::default();
if let TsNamespaceBody::TsModuleBlock(block) = body {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle dotted namespace reopens

When the reopened namespace is written in dotted form (namespace Outer.Inner { ... }), the Inner body is represented as TsNamespaceBody::TsNamespaceDecl, so this branch never pre-scans its exports and the existing visit_mut_ts_namespace_decl path never installs a shared namespace export scope for Inner. A pair such as namespace Outer.Inner { export const a = 1 } followed by namespace Outer.Inner { export const b = a } still leaves a unresolved/outer-scoped instead of resolving to Outer.Inner.a, even though TypeScript emits Inner.a for that case.

Useful? React with 👍 / 👎.

Comment on lines +94 to +97
scan.values.insert(f.ident.sym.clone());
}
Decl::Class(c) => {
scan.values.insert(c.ident.sym.clone());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Qualify reopened class and function exports

When a later namespace reopen references an exported class or function from an earlier reopen, adding these names to the shared value scope gives the reference the exported declaration's context, but the TypeScript transform still does not add ExportDecl classes/functions to semantic.exported_binding (unlike vars/enums/modules/import aliases), so ExportQuery never rewrites the later-body reference to N.C/N.f. For namespace N { export class C {} } namespace N { new C() }, the generated code keeps a free C inside the second IIFE instead of reading N.C, causing a runtime ReferenceError.

Useful? React with 👍 / 👎.

Comment on lines +500 to +501
if exp.declared_symbols.contains_key(sym) {
return Some(exp.mark);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Predeclare later namespace exports before lookups

This lookup only sees names already accumulated in the shared scope, so references in an earlier namespace body cannot resolve to exports declared by a later reopen. Valid var cases like namespace Test { export var b = a + 1 } namespace Test { export var a = 1 } should emit the first initializer as Test.a + 1; with the current one-pass cache, a is unresolved/outer-scoped while visiting the first body, producing a free a instead of the namespace property.

Useful? React with 👍 / 👎.

@magic-akari magic-akari left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added additional test cases that should pass according to the expected behavior, but they are failing on my machine.
This suggests the current implementation may not fully match the expected behavior and may require a fix.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24c58ea1c4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment on lines +73 to +76
if !import.is_type_only {
scan.values.insert(import.id.sym.clone());
}
scan.types.insert(import.id.sym.clone());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Populate type scope for exported import aliases

This records export import type A = ... as a namespace type export, but the declaration visitor for TsImportEqualsDecl only calls modify in value mode, so type-only aliases never get inserted into the shared declared_types scope. In a reopen such as namespace N { export import type A = Outer; } namespace N { export type T = A.I; }, A is not found through the merged namespace type scope and can fall through to an outer/unresolved type instead. Add a type-space declaration for exported import-equals aliases, and avoid creating a value binding when is_type_only is set.

Useful? React with 👍 / 👎.

Store one merged visibility table for each namespace identity. Each
exported name keeps the mark of the re-open that declares it. A
hoisting pre-pass seeds sibling exports into the table. Forward
references across re-opens then resolve. Namespace ids bind
body-locally and register in the table with a yield rule: a namespace
id must not overwrite a slot that a merged class, function, enum, or
interface owns. Exportedness for merge keying uses value exports only.

Bind a type-only `export import` alias in type space only. Add a
fixture for it.

Add two fixtures for declaration merging: a class that merges with a
namespace across re-opens, and a forward type reference to an
interface that shares its name with a namespace.

Update the tsxPreserveEmit1 snapshots. The reference to the exported
class X now resolves through the namespace and emits `M.X`. This
matches the comment in the fixture source.

Reformat namespace_reopen_nested_type_only_export/output.ts. The
codegen on main now prints empty interface bodies on two lines. All
marks in this fixture are unchanged.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9520ac537

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment on lines +704 to +707
let exported = self
.namespace_export_names
.as_ref()
.is_some_and(|names| names.values.contains_key(name));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Key nested namespace merges by namespace exports only

Here exported becomes true for any value export with the same name, but values also contains unrelated exports such as export import Inner = O, vars, classes, and enums. In a valid case like namespace Outer { export import Inner = O; namespace Inner { export const a = 1 } } const a = 2; namespace Outer { namespace Inner { export const b = a } }, the local Inner declarations must remain body-local and b should read the outer a; this check instead gives both local Inner bodies the exported merge table, so a resolves to the first private namespace and can be emitted as an out-of-scope Inner.a. Please base exported nested-namespace keying on the namespace declaration itself (for example namespace_ids), not every value export named the same.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. I checked this case against tsc 5.9.3. The example compiles clean, and swc
already produces the correct resolution and the correct emit:

  • Resolver: the two private Inner bodies stay body-local (Inner__4 / Inner__7),
    and b = a resolves to the outer const a (a__2).
  • Emit: Inner.b = a; with both private namespaces initialized body-locally, and
    Outer.Inner = O for the alias. This matches the tsc output.

The reason: the merged-table keying is only reachable for observable breakage through
programs that mix local and exported declarations of one name, and TypeScript rejects
those (error 2395). I added the example as a regression fixture
(namespace_reopen_private_export_import_shadow) so the behavior stays pinned.

*self
.namespace_instances
.borrow_mut()
.entry((keys.instance_scope, keys.name.clone()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Share exported nested namespace aliases across reopens

Keying the instance mark by the enclosing body gives an exported nested namespace a different local alias in each outer reopen, but references imported from the merged table keep the declaration body's member context. In namespace N { export namespace I { export const a = 1 } } namespace N { export namespace I { export const b = a } }, the second body resolves a to the first body's I context; the TypeScript transform then qualifies it with that first local alias, which is not in scope inside the second IIFE, instead of using the current N.I alias. Exported nested namespace instance marks need to be stable across parent reopens while private body scopes remain isolated.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. The old code keyed the instance mark by the enclosing body's mark, so each
parent re-open minted a distinct instance for the nested namespace. The emitted JS for
the example was:

(function(I1) {
    I1.b = I.a;   // `I` is body 1's alias, not in scope here
})(N.I || (N.I = {}));

I is unbound at runtime. Fixed by keying instance marks by (parent identity, name)
instead of (enclosing body mark, name). Top-level namespaces and private namespaces
derive the same key as before (their parent identity IS the enclosing scope's mark),
so only exported nested namespaces unify, which is the fix. The emit is now
I.b = I.a;, matching tsc.

On the transform side, a cross-body member reference is now qualified with the alias
of the body it appears in (a small stack of the namespace bodies under emit), not
with the declaring body's alias, and this works from nested bodies too.

NOTE (disclosure): this changes one token in the maintainer-provided fixture
namespace_reopen_nested_type_only_export: b__7 becomes b__5, i.e. b now
shares the nested instance mark with a (export const b__5 = a__5). The old pinned
resolution produced the out-of-scope emit above for the fixture's own
export const b = a line; the new resolution produces the tsc-equivalent emit. The
namespace declaration ids stay body-local (Inner__4 / Inner__6), as before.

let mut table = table.borrow_mut();
scan.values
.iter()
.filter(|(sym, _)| !scan.namespace_ids.contains(*sym))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Seed later exported namespaces before lookups

This filter intentionally drops namespace declarations from the forward seed, so an earlier reopen cannot resolve an exported namespace that is declared only by a later reopen. For namespace N { export const x = C } namespace N { export namespace C { export const a = 1 } } const C = "outer", TypeScript emits N.x = N.C, but the first body never gets C in the merged table here and will bind to the outer C (or remain unresolved). Please seed exported namespace ids as namespace-property bindings too, not only non-namespace exports.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. The forward seed skipped namespace ids, so x = C bound the outer
const C (N.x = C at runtime, the string "outer"; tsc emits N.x = N.C). Fixed by
seeding exported namespace ids as namespace-property bindings with the enclosing
instance mark, and registering the id's value-space table slot with that same mark in
bind_namespace_id. The id itself still binds body-locally (namespace declarations
do not unify across re-opens); only the member-reference view goes through the
instance mark. The yield rule for class/function/enum/interface merges is unchanged.

One refinement on top: only instantiated namespaces take a value-space slot. A
namespace whose members are (transitively) only interfaces, type aliases, and other
non-instantiated namespaces is fully erased, and tsc resolves a value reference to
its name past it to the outer binding. The check mirrors IsConcrete in the
TypeScript transform's retain module, so the resolver and the emitter agree. Both
declaration orders are pinned (namespace_reopen_erased_namespace_no_shadow,
namespace-reopen/type-only-namespace-no-shadow).

…dy member references

Round 2 of review feedback for the namespace scope merge.

Resolver (swc_ecma_transforms_base):

- Key the instance mark by (parent identity, name). Every re-open of an
  exported nested namespace now binds its exported value members with
  one shared instance mark. Before, each parent re-open minted its own
  mark, and the TypeScript transform emitted an alias that is not in
  scope at the reference site (`I1.b = I.a`).
- Seed exported namespace ids into the value side of the merged table.
  A reference in an earlier re-open then resolves a namespace that only
  a later sibling re-open declares.
- Give no value-space slot to a namespace that is not instantiated
  (transitively type-only; mirrors `IsConcrete` in the TypeScript
  transform). TypeScript erases such a namespace, and a value reference
  to its name must resolve to the outer binding.

TypeScript transform (swc_ecma_transforms_typescript):

- Record one shared member context per namespace body
  (`SemanticInfo::member_ctxt`) and the exported member names that keep
  local bindings in the emitted body (`SemanticInfo::local_member`,
  keyed by body span because re-opened declarations share an id). A
  post-pass shares the member context across re-opened bodies of one
  namespace, so bodies whose exports are only functions, classes, or
  namespaces also arm the rewrite.
- Qualify a cross-body member reference with the alias of the enclosing
  body under emit (`ExportQuery::namespace_stack`), not with the
  declaring body's alias. The stack makes this work from nested bodies.
- Keep a reference bare when the member keeps a local binding in the
  matched body (an exported function or class): the local binding is in
  scope, and function references rely on hoisting.
- Do not use the context-equality fast path for dotted bodies
  (`namespace A.B`): their members bind with the enclosing scope's
  mark, which every body-local binding shares.

New fixtures pin each behavior against the tsc 5.9.3 emit:
ts-resolver: namespace_reopen_private_export_import_shadow,
namespace_reopen_nested_reopen, namespace_reopen_late_namespace_export,
namespace_reopen_erased_namespace_no_shadow; strip:
namespace-reopen/{private-alias-shadow,nested-reopen,
late-namespace-export,dotted-body-locals,fn-class-members,
fn-class-only-bodies,type-only-namespace-no-shadow,nested-chain,
nested-fn-ref}.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 471e14d553

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment on lines +699 to +703
let frame = self
.semantic
.member_ctxt
.get(id)
.filter(|ctxt| **ctxt != id.1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Qualify namespace-only parent exports

When a reopened namespace only exports nested namespaces, semantic.member_ctxt never gets an entry for the parent namespace, so this lookup returns None and no parent frame is pushed. In namespace N { export namespace C { export const a = 1 } } namespace N { export namespace D { export const x = C } } const C = "outer", the resolver gives the C reference the parent namespace member context, but the transform has only D's frame and leaves C bare; tsc emits D.x = N.C, while SWC would bind to the outer/free C instead.

Useful? React with 👍 / 👎.

Comment on lines +939 to +944
let scan = pre_scan_namespace_exports(body);
let mut table = table.borrow_mut();
scan.values
.iter()
.filter(|(sym, _)| !scan.erased_namespace_ids.contains(*sym))
.for_each(|(sym, kind)| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Seed exports inside dotted namespace bodies

For dotted reopens, this seed only scans the outer synthetic body, so a later namespace A.B { export var y = 1 } contributes B to A but never predeclares y in B's merged table. An earlier namespace A.B { export var x = y } therefore resolves y to an outer binding or leaves it unresolved, even though tsc emits B.x = B.y for this valid var forward-reference across dotted namespace reopens.

Useful? React with 👍 / 👎.

.semantic
.member_ctxt
.get(id)
.filter(|ctxt| **ctxt != id.1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Qualify class members in dotted reopens

This filter prevents dotted namespace bodies from ever pushing a namespace frame, but exported classes/functions are deliberately absent from exported_binding, so cross-body refs to them have no fallback rewrite. In namespace Outer.Inner { export class C {} } namespace Outer.Inner { export const c = new C(); }, the resolver resolves C to the first dotted body's context, then the transform leaves new C() in the second IIFE instead of new Inner.C(), causing a runtime ReferenceError.

Useful? React with 👍 / 👎.

CI compiles swc_ecma_transforms with `--cfg swc_ast_unknown`. Under
that cfg, `VarDeclKind` is non-exhaustive, and the `match` in
`add_decl_export_names` did not compile (E0004). Use the existing
`impl From<VarDeclKind> for DeclKind` in scope.rs instead. That impl
holds the same mapping and already handles the cfg. This keeps one
source of truth and does not change behavior for `Var`, `Let`, or
`Const`.

Regenerate the `tsxEmit3` baselines in
`crates/swc/tests/tsc-references`. The old baselines held bare
cross-body references (`Foo`, `Bar`) that throw a ReferenceError at
run time. This branch now qualifies them (`M.Foo`, `S.Bar`, and
`M.Foo` beside the hygiene-renamed local `M1`). This agrees with the
intent comments in the fixture (`// Emit M.Foo`, `// Emit S.Bar`,
`// Emit M_1.Foo`) and with the tsc emit for the same source.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bcd1935d38

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment on lines +230 to +233
|| decl
.as_fn_decl()
.map(|function_decl| function_decl.function.body.is_none())
.unwrap_or_default()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat ambient namespace functions as instantiating

When an exported nested namespace contains only ambient function members, e.g. namespace N { export const x = C } namespace N { export namespace C { export declare function f(): void } }, this predicate classifies the C body as non-instantiated because the function has no body. That puts C in erased_namespace_ids, so the forward seed skips the namespace value and the earlier C reference can bind to an outer/free C; TypeScript still creates the namespace object for ambient function members and emits the reference as N.C.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed. I verified the claim against tsc 5.9.3 first: a
namespace whose only member is export declare function f(): void gets
its IIFE, and an earlier sibling reopen reference is emitted qualified
(N.x = N.C). The same holds for a bare declare function, for a
declare namespace member (qualified reference, no IIFE for the ambient
member itself), and transitively for deep nesting.

The fix removes the body check from decl_is_instantiating: every
function declaration now counts as instantiating, which matches tsc. To
keep the resolver and the emitter in agreement by construction,
ts_module_is_instantiated is now public and
TsModuleDecl::is_concrete in the strip pass delegates to it. Statement
retention of overload signatures is unchanged; only the namespace
instantiation aggregate changed.

Two existing tsc reference baselines churned, and I checked both against
the real tsc emit before regenerating: parserFunctionDeclaration7
(module M { function foo(); } now gets var M; plus its IIFE, which
is exactly what tsc emits) and neverReturningFunctions1 (the Debug
namespace, whose only member is export declare function fail, now gets
its IIFE; its Debug.fail references were already qualified in the old
baseline, so the old output was a ReferenceError at runtime).

New fixtures: namespace-reopen/ambient-fn-namespace (covers both the
export and the plain statement path), namespace-reopen/declare-namespace-member,
namespace-reopen/deep-ambient-fn (strip), and
namespace_reopen_ambient_fn_instantiates (resolver, a direct
differential against the existing erased-namespace fixture: the
interface-only namespace still lets the reference escape to the outer
binding, the ambient-fn namespace no longer does).

Comment on lines +947 to +949
scan.types.iter().for_each(|sym| {
table.types.entry(sym.clone()).or_insert(instance_mark);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Seed namespace ids into the merged type scope

This only pre-seeds scan.types, but exported namespace declarations are intentionally kept out of that set, so an earlier reopen cannot resolve a type reference to a namespace that is declared only by a later reopen. For example namespace N { export type T = C.I } namespace N { export namespace C { export interface I {} } } is accepted by TypeScript, but while resolving the first body C is not in the merged type table yet and can remain unresolved or bind to an outer type. Please seed namespace_ids into the type table as well, using the namespace declaration's eventual type mark semantics.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed. Verified against tsc 5.9.3 with a
positive/negative pair: a forward type reference C.I from an earlier
reopen resolves to the namespace C declared only by a later reopen
(compiles clean), and the negative control that would only compile if
C.I bound an outer C is rejected. So the earlier reopen must see the
later sibling, which the old code did not provide.

The fix seeds scan.namespace_ids into the merged type table in the
hoisting pre-pass, mirroring the existing value-side seed. Three
deliberate properties:

  1. The seed only fills vacant slots and records namespace ownership, so
    the declaring body's own registration still overwrites it with the
    body mark through the existing ownership mechanism (forward
    references get the instance mark, backward references the body mark,
    the same contract as the value side).
  2. A slot already claimed by a non-namespace type declaration is left
    alone, preserving the existing "explicit type declaration wins"
    behavior.
  3. The seed has no erased-namespace filter, on purpose: erasure removes
    the value meaning but keeps the type meaning, and a type-only
    namespace is exactly the case the comment's example exercises.

New fixture: namespace_reopen_forward_type_ref (resolver), covering
an erased namespace shadowing an import alias, an instantiated
namespace variant, and a backward reference (namespace declared in an
earlier reopen, type reference in a later one) that pins the
forward-gets-instance-mark / backward-gets-body-mark contract.

While landing this I ran an adversarial self-review over the seed and
found two follow-on defects, both fixed here:

  1. When a later reopen declares a real interface or type alias with a
    seeded namespace name, the slot kept namespace ownership, so the
    namespace registration clobbered the merged slot with its body mark
    and references disagreed with the interface declaration. A
    non-namespace type declaration seen by any reopen now revokes
    namespace ownership, in either seeding order; the reference and the
    merged interface agree on the instance mark
    (namespace_reopen_ns_iface_type_merge pins it, tsc-verified).
  2. The seed made a bare type reference resolve into a sibling
    namespace. tsc gives a namespace-only symbol no type meaning
    (TS2709) and resolves the reference past it to an outer type
    binding, so bare type references now skip merged type slots that
    only a namespace owns, while the left side of a qualified name
    still resolves with namespace meaning and binds them
    (namespace_reopen_bare_type_ref_outer pins both directions,
    tsc-verified with a TS2709 negative control).

…espace ids into the merged type scope

Address the two Codex P2 review comments (round 4), plus three defects
that an adversarial self-review found in the same machinery.

P2-1: tsc classifies every function declaration as instantiating, with
or without a body. Remove the body check from decl_is_instantiating. A
namespace whose only member is an ambient function declaration now
counts as instantiated. The hoisting pre-pass seeds its value slot, so
an earlier sibling reopen binds the namespace instead of an outer
binding. Make ts_module_is_instantiated public and delegate
TsModuleDecl::is_concrete in the strip pass to it. The resolver and the
emitter now classify namespaces identically by construction. Statement
retention of overload signatures does not change.

P2-2: seed scan.namespace_ids into the merged type table during the
hoisting pre-pass. The seed only fills vacant slots, uses the instance
mark, and records namespace ownership. The declaring body later
overwrites the slot with its body mark through the existing ownership
mechanism. A type slot that a non-namespace type declaration already
claimed stays untouched. The seed applies to erased namespaces too,
because erasure removes the value meaning and keeps the type meaning.

Self-review fixes in the same machinery:

1. Value seed: the erased-name filter kept a name erased when any
   occurrence of it was a non-instantiated namespace declaration, even
   when a function, class, enum, or an instantiated namespace
   occurrence in the same body gave the merged symbol value meaning. A
   forward value reference then fell through to an outer binding or to
   an unresolved identifier. The scan now tracks value meaning per
   name, in either declaration order, and a namespace declaration no
   longer overwrites the declaration kind that a merged function
   recorded.

2. Type seed ownership: when a later re-open declared an interface or
   a type alias with a seeded namespace name, the slot kept namespace
   ownership, so the namespace registration clobbered the merged slot
   with its body mark and references disagreed with the type
   declaration. A non-namespace type declaration seen by any re-open
   now revokes namespace ownership, in either seeding order, and every
   reference agrees with the merged declaration on the instance mark.

3. Bare type references: the type seed made a bare type reference
   resolve into a sibling namespace. tsc gives a namespace-only symbol
   no type meaning (TS2709) and resolves the reference past it to an
   outer type binding. Bare type references now skip merged type slots
   that only a namespace owns; the left side of a qualified name still
   resolves with namespace meaning and binds them.

Verified against tsc 5.9.3 for all changes, with positive and negative
controls. Two existing tsc reference baselines change and both now match
the tsc emit: parserFunctionDeclaration7 (module M with only an overload
signature gets its IIFE) and neverReturningFunctions1 (the Debug
namespace gets its IIFE; its references were already qualified).

New fixtures: ambient-fn-namespace, declare-namespace-member,
deep-ambient-fn, and fn-ns-value-merge (strip);
namespace_reopen_ambient_fn_instantiates,
namespace_reopen_forward_type_ref, namespace_reopen_fn_ns_value_merge,
namespace_reopen_ns_iface_type_merge, and
namespace_reopen_bare_type_ref_outer (resolver).

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
@MavenRain

Copy link
Copy Markdown
Contributor Author

One more fix in this push, from the same self-review of the merge
machinery, on the value side: the erased-namespace filter kept a name
erased when any occurrence of it was a non-instantiated namespace
declaration, even when the merged symbol had value meaning from a
function, class, enum, or an instantiated occurrence of the same
namespace. For

namespace N {
    export const x = foo;
}
namespace N {
    export function foo() { return 1; }
    export namespace foo { export interface Options {} }
}

tsc emits N.x = N.foo;, but the seed dropped foo entirely and the
reference fell through to an outer binding (or an unresolved
identifier). The scan now tracks value meaning per name, in either
declaration order. Fixtures: namespace-reopen/fn-ns-value-merge
(strip, also covers a non-instantiated then instantiated re-declaration
of the same namespace) and namespace_reopen_fn_ns_value_merge
(resolver), both matching the tsc 5.9.3 emit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

SWC Resolver Incorrectly Resolves Cross-Namespace References

4 participants