Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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: 7 additions & 0 deletions .chronus/changes/fix-using-blockless-namespace-2026-7-30.md
Comment thread
timotheeguerin marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/compiler"
---

Fix false-positive `duplicate-using` diagnostic when a `using` statement appears before a file-level (blockless) namespace declaration and the same namespace is also used inside the file namespace.
15 changes: 14 additions & 1 deletion packages/compiler/src/core/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4962,9 +4962,22 @@ export function createChecker(program: Program, resolver: NameResolver): Checker
return newTacker;
}

// The file-level (blockless) namespace node, if any. A using that appears after
// this namespace declaration is scoped to it (like a using inside a block namespace),
// and should be tracked separately from usings that appear before it.
const fileNamespaceNode = file.inScopeNamespaces[0];

for (const using of file.usings) {
const ns = using.parent!;
const sym = getMergedSymbol(ns.symbol);
// If the using appears after the blockless namespace declaration, treat it as
// scoped to that namespace for duplicate detection. This is consistent with how
// usings inside block namespaces are handled and avoids false-positive
// duplicate-using errors when the same namespace is imported both before and
// after a blockless namespace declaration.
const sym =
fileNamespaceNode !== undefined && using.pos >= fileNamespaceNode.pos
? getMergedSymbol(fileNamespaceNode.symbol)
: getMergedSymbol(ns.symbol);
const tracker = getTracker(sym);
const targetSym = resolver.getNodeLinks(using.name).resolvedSymbol;
if (!targetSym) continue;
Expand Down
35 changes: 35 additions & 0 deletions packages/compiler/test/checker/using.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,41 @@ describe("compiler: using statements", () => {
expectDiagnosticEmpty(diagnostics);
});

it("doesn't consider using before file namespace and using inside file namespace as duplicate", async () => {
const diagnostics = await Tester.files({
"a.tsp": `namespace A { model AModel {} }`,
"b.tsp": `
import "./a.tsp";
using A;
namespace B;
using A;
`,
}).diagnose(`
import "./a.tsp";
import "./b.tsp";
`);
expectDiagnosticEmpty(diagnostics);
});

it("throws errors for duplicate usings inside a blockless namespace", async () => {
const diagnostics = await Tester.files({
"a.tsp": `namespace A { model AModel {} }`,
"b.tsp": `
import "./a.tsp";
namespace B;
using A;
using A;
`,
}).diagnose(`
import "./a.tsp";
import "./b.tsp";
`);
expectDiagnostics(diagnostics, [
{ code: "duplicate-using", message: 'duplicate using of "A" namespace' },
{ code: "duplicate-using", message: 'duplicate using of "A" namespace' },
]);
});

it("throws errors for duplicate imported usings", async () => {
const diagnostics = await Tester.files({
"a.tsp": `
Expand Down