Skip to content

Fix #665 (nested namespace bare-name → enclosing scope); complete #639#748

Merged
nickna merged 1 commit into
mainfrom
wrk/vigorous-sinoussi-1362f6
Jun 16, 2026
Merged

Fix #665 (nested namespace bare-name → enclosing scope); complete #639#748
nickna merged 1 commit into
mainfrom
wrk/vigorous-sinoussi-1362f6

Conversation

@nickna

@nickna nickna commented Jun 16, 2026

Copy link
Copy Markdown
Owner

#665 — nested namespace member cannot reference an enclosing namespace's member by bare name

A function in a nested namespace could not reference a member of its enclosing namespace by bare (unqualified) name — rejected at type-check as Undefined variable in both modes. tsc accepts it (enclosing members are in lexical scope of nested bodies).

namespace O {
  function outerHelp() { return 7; }            // non-exported member of O
  export namespace I {
    export function f() { return outerHelp(); }  // bare ref to O.outerHelp
  }
}
console.log(O.I.f());   // tsc: 7 — now 7 in both modes

Root cause (type checker): CheckNamespace fully checks any nested namespace during the enclosing namespace's first pass (CollectNamespaceMemberType → recursive CheckNamespace), but member functions were only registered in the second pass — so nested bodies saw nothing. Fix: CheckNamespace now HoistFunctionDeclarations(ns.Members) before the first pass, mirroring the top-level hoisting order. (var/const/class/enum already resolved.)

Compiled codegen: a bare reference to a sibling/enclosing namespace object (A.g() from O.B.f, where A is O.A) type-checked but threw at runtime — namespace fields are keyed by full dotted path ($ns_O_A), so the simple name never matched. New CompilationContext.ResolveNamespaceField walks the current namespace path innermost→outermost (mirroring ResolveFunctionName), wired into both bare-variable sites (ILEmitter.Expressions + ExpressionEmitterBase) so the sync and state-machine paths stay in sync. Also covers new A.X() from a sibling namespace.

6 new both-mode tests (SharedTests/NamespaceTests.cs): issue repro, forward reference, enclosing class, sibling namespace, 3-level nesting, inner-shadows-outer.

#639 — branded-target collector with a generic-instantiation superclass

The substantive collector fix already landed on main (#655). This completes the issue's remaining minor/related note: the MutableClass branch of GetMemberType (signature-collection-time) walked frozen.Superclass with unsubstituted GetFieldTypes/GetMethods, surfacing an inherited generic-base member as the bare type parameter. Routed it through the existing substitution-aware ResolveClassMemberTypeSubstituted, unifying it with the frozen-instance path and the member-set collectors. Equivalent for a non-generic superclass; not independently observable (matches the issue's "transient" framing), validated by the full suite.

Follow-ups filed

Testing

Closes #665. Closes #639.

…complete #639

#665 (type-checker name resolution): a function in a NESTED namespace could not
reference a member of its ENCLOSING namespace by bare name (the issue repro:
`O.I.f` calling `O`'s non-exported `outerHelp()`), rejected as "Undefined
variable" in both modes. Root cause: `CheckNamespace` fully checks any nested
namespace during the enclosing namespace's FIRST pass (CollectNamespaceMemberType
→ recursive CheckNamespace), but member functions were only registered in the
SECOND pass — so nested bodies saw nothing. CheckNamespace now hoists function
declarations (HoistFunctionDeclarations) before the first pass, mirroring the
top-level order. var/const/class/enum already resolved (hoisted as `any` /
first-pass source-order registration).

Compiled codegen: a bare reference to a sibling/enclosing NAMESPACE OBJECT
(`A.g()` from `O.B.f`, where A is `O.A`) type-checked (namespaces register
eagerly) but threw at runtime — namespace fields are keyed by full dotted path
(`$ns_O_A`), so the simple name never matched. New
`CompilationContext.ResolveNamespaceField` walks the current namespace path
innermost→outermost (mirroring ResolveFunctionName), wired into both bare-variable
resolution sites (ILEmitter.Expressions + ExpressionEmitterBase) so the sync and
state-machine paths stay in sync. Also covers `new A.X()` from a sibling namespace.

6 new both-mode tests in SharedTests/NamespaceTests.cs (issue repro, forward
reference, enclosing class, sibling namespace, 3-level nesting, inner-shadows-outer).

#639 (branded-target collector): the substantive collector fix already landed on
main in #655 (CollectAllInstanceMembers folds a generic-instantiation superclass;
CollectGenericClassMembers gained an all-members mode) with tests. This completes
the issue's remaining "minor/related" note: the MutableClass branch of GetMemberType
(signature-collection-time) walked frozen.Superclass with unsubstituted
GetFieldTypes/GetMethods, surfacing an inherited generic-base member as the bare
type parameter. Routed it through the existing substitution-aware
ResolveClassMemberTypeSubstituted, unifying it with the frozen-instance path and the
member-set collectors. Equivalent for a non-generic superclass; not independently
observable (matches the issue's "transient" framing), validated by the full suite.

Filed follow-ups: #745 (nested ns cannot name its enclosing ns by its own name —
the namespace self-binds only at the END of CheckNamespace), #746 (interpreter
mis-resolves a nested ns shadowing a same-named top-level ns; compiled is correct).

Full suite: 13033 passed / 0 failed.
@nickna nickna merged commit 7f7ac66 into main Jun 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant