Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/compiler"
---

Fix false-positive name resolution failure when a `using` statement references a namespace whose first segment coincides with an intermediate segment in a multi-segment blockless namespace declaration (e.g. `using TypeSpec.Http` in a file with `namespace _Specs_.TypeSpec.Bar;`). The intermediate namespace segment `_Specs_.TypeSpec` was incorrectly shadowing the global `TypeSpec` namespace through the `inScopeNamespaces` lookup.
21 changes: 16 additions & 5 deletions packages/compiler/src/core/name-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1093,11 +1093,12 @@ export function createResolver(program: Program): NameResolver {
}

if (!binding && scope && scope.kind === SyntaxKind.TypeSpecScript) {
// check any blockless namespace decls
for (const ns of scope.inScopeNamespaces) {
const mergedSymbol = getMergedSymbol(ns.symbol);
binding = tableLookup(mergedSymbol.exports!, node, options.resolveDecorators);

// check the leaf (innermost) blockless namespace decl first — this mirrors how
// block-namespace scoping works: the current namespace's own exports take priority.
if (scope.inScopeNamespaces.length > 0) {
const leafNs = scope.inScopeNamespaces[0];
const mergedLeafSymbol = getMergedSymbol(leafNs.symbol);
binding = tableLookup(mergedLeafSymbol.exports!, node, options.resolveDecorators);
if (binding) return resolvedResult(binding);
}

Expand Down Expand Up @@ -1128,6 +1129,16 @@ export function createResolver(program: Program): NameResolver {
}
return resolvedResult(usingBinding.symbolSource!);
}

// check parent blockless namespace decls (after global scope and usings so that
// global names are not shadowed by intermediate namespace segments in a multi-segment
// blockless namespace path such as `namespace A.TypeSpec.B;`)
for (let i = 1; i < scope.inScopeNamespaces.length; i++) {
const ns = scope.inScopeNamespaces[i];
const mergedSymbol = getMergedSymbol(ns.symbol);
binding = tableLookup(mergedSymbol.exports!, node, options.resolveDecorators);
if (binding) return resolvedResult(binding);
}
}

return failedResult(ResolutionResultFlags.Unknown);
Expand Down
28 changes: 27 additions & 1 deletion packages/compiler/test/checker/namespaces.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { describe, it } from "vitest";
import type { Program } from "../../src/core/program.js";
import type { Type } from "../../src/core/types.js";
import { getTypeName } from "../../src/index.js";
import { expectDiagnostics, expectTypeEquals, mockFile, t } from "../../src/testing/index.js";
import {
expectDiagnosticEmpty,
expectDiagnostics,
expectTypeEquals,
mockFile,
t,
} from "../../src/testing/index.js";
import { Tester } from "../tester.js";

describe("compiler: namespaces with blocks", () => {
Expand Down Expand Up @@ -341,6 +347,26 @@ describe("compiler: blockless namespaces", () => {
strictEqual(Foo.models.size, 1);
strictEqual(Foo.namespaces.size, 1);
});

it("does not let intermediate namespace segments shadow global namespace names in using statements", async () => {
Comment thread
timotheeguerin marked this conversation as resolved.
// Regression test: `namespace _Specs_.TypeSpec.Bar;` creates `_Specs_.TypeSpec` as an
// intermediate segment. Previously `using TypeSpec.Http` would incorrectly resolve
// `TypeSpec` to `_Specs_.TypeSpec` (found via inScopeNamespaces ancestor) instead of
// the global `TypeSpec` namespace.
const diagnostics = await Tester.files({
"http.tsp": `
namespace TypeSpec.Http {
model HttpModel {}
}
`,
}).diagnose(`
import "./http.tsp";
using TypeSpec.Http;
namespace _Specs_.TypeSpec.Bar;
model M extends HttpModel {}
`);
expectDiagnosticEmpty(diagnostics);
});
});

describe("compiler: namespace type name", () => {
Expand Down