Skip to content
Open
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
7 changes: 5 additions & 2 deletions Execution/Interpreter.Namespaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ private ExecutionResult ExecuteNamespace(Stmt.Namespace ns)
{
string name = ns.Name.Lexeme;

// Get or create namespace object
SharpTSNamespace? existingNs = _environment.GetNamespace(name);
// Get or create namespace object — check ONLY the current scope, not up the chain.
// GetNamespace walks up and would find a same-named outer namespace (e.g. top-level A
// when declaring O.A), incorrectly merging the nested namespace into the outer one and
// leaving the current scope without an "A" binding (#746).
SharpTSNamespace? existingNs = _environment.GetLocalNamespace(name);
SharpTSNamespace nsObj;

if (existingNs != null)
Expand Down
11 changes: 11 additions & 0 deletions Runtime/RuntimeEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ public void DefineNamespace(string name, SharpTSNamespace ns)
return Enclosing?.GetNamespace(name);
}

/// <summary>
/// Gets a namespace by name from THIS scope only (no chain traversal).
/// Use when deciding whether to merge vs. create a new namespace declaration —
/// avoids treating a same-named namespace in an enclosing scope as a merge target (#746).
/// </summary>
public SharpTSNamespace? GetLocalNamespace(string name)
{
_namespaces.TryGetValue(name, out var ns);
return ns;
}

/// <summary>
/// Defines a variable with a boxed value (legacy compatibility).
/// Wraps the value in RuntimeValue.FromBoxed automatically.
Expand Down
20 changes: 20 additions & 0 deletions SharpTS.Tests/SharedTests/NamespaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -796,5 +796,25 @@ export namespace I {
Assert.Equal("2\n", TestHarness.Run(code, mode));
}

[Theory]
[MemberData(nameof(ExecutionModes.All), MemberType = typeof(ExecutionModes))]
public void NestedNamespace_ShadowsSameNamedTopLevelNamespace(ExecutionMode mode)
{
// Nested O.A shadows the top-level A for bare references from sibling O.B.
// The interpreter previously merged O.A's members into the global A and left
// namespaceEnvO without an "A" binding, so the pre-resolved distance-2 lookup
// returned Undefined → "Only instances and objects have properties" (#746).
var code = @"
namespace A { export function g() { return 100; } }
namespace O {
export namespace A { export function g() { return 5; } }
export namespace B { export function f() { return A.g(); } }
}
console.log(O.B.f());
console.log(A.g());
";
Assert.Equal("5\n100\n", TestHarness.Run(code, mode));
}

#endregion
}
Loading