From e038de5e33e19cea6022a025f64d81f895b1475c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 18 Oct 2025 17:51:14 -0400 Subject: [PATCH 1/3] Consistently error on full circle of circular import aliases --- internal/ast/symbol.go | 1 - internal/checker/checker.go | 23 ++++++++++------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/internal/ast/symbol.go b/internal/ast/symbol.go index 926d9bd53a..56cfb2ba1b 100644 --- a/internal/ast/symbol.go +++ b/internal/ast/symbol.go @@ -43,7 +43,6 @@ const ( InternalSymbolNameClass = InternalSymbolNamePrefix + "class" // Unnamed class expression InternalSymbolNameFunction = InternalSymbolNamePrefix + "function" // Unnamed function expression InternalSymbolNameComputed = InternalSymbolNamePrefix + "computed" // Computed property name declaration with dynamic name - InternalSymbolNameResolving = InternalSymbolNamePrefix + "resolving" // Indicator symbol used to mark partially resolved type aliases InternalSymbolNameInstantiationExpression = InternalSymbolNamePrefix + "instantiationExpression" // Instantiation expressions InternalSymbolNameImportAttributes = InternalSymbolNamePrefix + "importAttributes" InternalSymbolNameExportEquals = "export=" // Export assignment symbol diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 3d56796e37..1c33f48382 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -61,6 +61,7 @@ const ( TypeSystemPropertyNameResolvedBaseTypes TypeSystemPropertyNameWriteType TypeSystemPropertyNameInitializerIsUndefined + TypeSystemPropertyNameAliasTarget ) type TypeResolution struct { @@ -618,7 +619,6 @@ type Checker struct { argumentsSymbol *ast.Symbol requireSymbol *ast.Symbol unknownSymbol *ast.Symbol - resolvingSymbol *ast.Symbol unresolvedSymbols map[string]*ast.Symbol errorTypes map[string]*Type globalThisSymbol *ast.Symbol @@ -915,7 +915,6 @@ func NewChecker(program Program) *Checker { c.argumentsSymbol = c.newSymbol(ast.SymbolFlagsProperty, "arguments") c.requireSymbol = c.newSymbol(ast.SymbolFlagsProperty, "require") c.unknownSymbol = c.newSymbol(ast.SymbolFlagsProperty, "unknown") - c.resolvingSymbol = c.newSymbol(ast.SymbolFlagsNone, ast.InternalSymbolNameResolving) c.unresolvedSymbols = make(map[string]*ast.Symbol) c.errorTypes = make(map[string]*Type) c.globalThisSymbol = c.newSymbolEx(ast.SymbolFlagsModule, "globalThis", ast.CheckFlagsReadonly) @@ -15634,29 +15633,25 @@ func (c *Checker) resolveAlias(symbol *ast.Symbol) *ast.Symbol { } links := c.aliasSymbolLinks.Get(symbol) if links.aliasTarget == nil { - links.aliasTarget = c.resolvingSymbol + if !c.pushTypeResolution(symbol, TypeSystemPropertyNameAliasTarget) { + return c.unknownSymbol + } node := c.getDeclarationOfAliasSymbol(symbol) if node == nil { panic("Unexpected nil in resolveAlias for symbol: " + c.symbolToString(symbol)) } - target := c.getTargetOfAliasDeclaration(node, false /*dontRecursivelyResolve*/) - if links.aliasTarget == c.resolvingSymbol { - if target == nil { - target = c.unknownSymbol - } - links.aliasTarget = target - } else { + links.aliasTarget = core.OrElse(c.getTargetOfAliasDeclaration(node, false /*dontRecursivelyResolve*/), c.unknownSymbol) + if !c.popTypeResolution() { c.error(node, diagnostics.Circular_definition_of_import_alias_0, c.symbolToString(symbol)) + links.aliasTarget = c.unknownSymbol } - } else if links.aliasTarget == c.resolvingSymbol { - links.aliasTarget = c.unknownSymbol } return links.aliasTarget } func (c *Checker) tryResolveAlias(symbol *ast.Symbol) *ast.Symbol { links := c.aliasSymbolLinks.Get(symbol) - if links.aliasTarget != c.resolvingSymbol { + if links.aliasTarget != nil || c.findResolutionCycleStartIndex(symbol, TypeSystemPropertyNameAliasTarget) < 0 { return c.resolveAlias(symbol) } return nil @@ -18079,6 +18074,8 @@ func (c *Checker) typeResolutionHasProperty(r *TypeResolution) bool { return c.nodeLinks.Get(r.target.(*ast.Node)).flags&NodeCheckFlagsInitializerIsUndefinedComputed != 0 case TypeSystemPropertyNameWriteType: return c.valueSymbolLinks.Get(r.target.(*ast.Symbol)).writeType != nil + case TypeSystemPropertyNameAliasTarget: + return c.aliasSymbolLinks.Get(r.target.(*ast.Symbol)).aliasTarget != nil } panic("Unhandled case in typeResolutionHasProperty") } From 312160daa935ef367604fad4fc36ab2203dea743 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 18 Oct 2025 17:51:36 -0400 Subject: [PATCH 2/3] Accept new baselines --- .../compiler/circularModuleImports.errors.txt | 5 +++- .../circularModuleImports.errors.txt.diff | 23 +++++++++++++++ .../declarationEmitUnknownImport.errors.txt | 7 +++-- ...clarationEmitUnknownImport.errors.txt.diff | 22 ++++++++++++++ .../declarationEmitUnknownImport2.errors.txt | 7 +++-- ...larationEmitUnknownImport2.errors.txt.diff | 22 ++++++++++++++ .../exportAsNamespaceConflict.errors.txt | 5 +++- .../exportAsNamespaceConflict.errors.txt.diff | 16 ++++++++++ .../conformance/circular1.errors.txt | 5 +++- .../conformance/circular1.errors.txt.diff | 15 ++++++++++ .../conformance/circular3.errors.txt | 13 +++++++-- .../conformance/circular3.errors.txt.diff | 29 +++++++++++++++++++ 12 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 testdata/baselines/reference/submodule/compiler/circularModuleImports.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport2.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/compiler/exportAsNamespaceConflict.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/conformance/circular1.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/conformance/circular3.errors.txt.diff diff --git a/testdata/baselines/reference/submodule/compiler/circularModuleImports.errors.txt b/testdata/baselines/reference/submodule/compiler/circularModuleImports.errors.txt index d7fdcefcfd..2629296c6b 100644 --- a/testdata/baselines/reference/submodule/compiler/circularModuleImports.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/circularModuleImports.errors.txt @@ -1,7 +1,8 @@ circularModuleImports.ts(5,5): error TS2303: Circular definition of import alias 'A'. +circularModuleImports.ts(7,5): error TS2303: Circular definition of import alias 'B'. -==== circularModuleImports.ts (1 errors) ==== +==== circularModuleImports.ts (2 errors) ==== module M { @@ -11,6 +12,8 @@ circularModuleImports.ts(5,5): error TS2303: Circular definition of import alias !!! error TS2303: Circular definition of import alias 'A'. import B = A; + ~~~~~~~~~~~~~ +!!! error TS2303: Circular definition of import alias 'B'. } \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/circularModuleImports.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/circularModuleImports.errors.txt.diff new file mode 100644 index 0000000000..dd48f7b787 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/circularModuleImports.errors.txt.diff @@ -0,0 +1,23 @@ +--- old.circularModuleImports.errors.txt ++++ new.circularModuleImports.errors.txt +@@= skipped -0, +0 lines =@@ + circularModuleImports.ts(5,5): error TS2303: Circular definition of import alias 'A'. +- +- +-==== circularModuleImports.ts (1 errors) ==== ++circularModuleImports.ts(7,5): error TS2303: Circular definition of import alias 'B'. ++ ++ ++==== circularModuleImports.ts (2 errors) ==== + module M + + { +@@= skipped -10, +11 lines =@@ + !!! error TS2303: Circular definition of import alias 'A'. + + import B = A; ++ ~~~~~~~~~~~~~ ++!!! error TS2303: Circular definition of import alias 'B'. + + } + \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport.errors.txt b/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport.errors.txt index ffde8f5e80..1826289e03 100644 --- a/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport.errors.txt @@ -1,9 +1,10 @@ declarationEmitUnknownImport.ts(1,1): error TS2303: Circular definition of import alias 'Foo'. declarationEmitUnknownImport.ts(1,14): error TS2304: Cannot find name 'SomeNonExistingName'. declarationEmitUnknownImport.ts(1,14): error TS2503: Cannot find namespace 'SomeNonExistingName'. +declarationEmitUnknownImport.ts(2,9): error TS2303: Circular definition of import alias 'Foo'. -==== declarationEmitUnknownImport.ts (3 errors) ==== +==== declarationEmitUnknownImport.ts (4 errors) ==== import Foo = SomeNonExistingName ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2303: Circular definition of import alias 'Foo'. @@ -11,4 +12,6 @@ declarationEmitUnknownImport.ts(1,14): error TS2503: Cannot find namespace 'Some !!! error TS2304: Cannot find name 'SomeNonExistingName'. ~~~~~~~~~~~~~~~~~~~ !!! error TS2503: Cannot find namespace 'SomeNonExistingName'. - export {Foo} \ No newline at end of file + export {Foo} + ~~~ +!!! error TS2303: Circular definition of import alias 'Foo'. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport.errors.txt.diff new file mode 100644 index 0000000000..486c3ff82d --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport.errors.txt.diff @@ -0,0 +1,22 @@ +--- old.declarationEmitUnknownImport.errors.txt ++++ new.declarationEmitUnknownImport.errors.txt +@@= skipped -0, +0 lines =@@ + declarationEmitUnknownImport.ts(1,1): error TS2303: Circular definition of import alias 'Foo'. + declarationEmitUnknownImport.ts(1,14): error TS2304: Cannot find name 'SomeNonExistingName'. + declarationEmitUnknownImport.ts(1,14): error TS2503: Cannot find namespace 'SomeNonExistingName'. +- +- +-==== declarationEmitUnknownImport.ts (3 errors) ==== ++declarationEmitUnknownImport.ts(2,9): error TS2303: Circular definition of import alias 'Foo'. ++ ++ ++==== declarationEmitUnknownImport.ts (4 errors) ==== + import Foo = SomeNonExistingName + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + !!! error TS2303: Circular definition of import alias 'Foo'. +@@= skipped -11, +12 lines =@@ + ~~~~~~~~~~~~~~~~~~~ + !!! error TS2503: Cannot find namespace 'SomeNonExistingName'. + export {Foo} ++ ~~~ ++!!! error TS2303: Circular definition of import alias 'Foo'. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport2.errors.txt b/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport2.errors.txt index 37d7841f4b..f52b6fd9af 100644 --- a/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport2.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport2.errors.txt @@ -3,9 +3,10 @@ declarationEmitUnknownImport2.ts(1,12): error TS1005: '=' expected. declarationEmitUnknownImport2.ts(1,12): error TS2304: Cannot find name 'From'. declarationEmitUnknownImport2.ts(1,12): error TS2503: Cannot find namespace 'From'. declarationEmitUnknownImport2.ts(1,17): error TS1005: ';' expected. +declarationEmitUnknownImport2.ts(2,1): error TS2303: Circular definition of import alias 'Foo'. -==== declarationEmitUnknownImport2.ts (5 errors) ==== +==== declarationEmitUnknownImport2.ts (6 errors) ==== import Foo From './Foo'; // Syntax error ~~~~~~~~~~~~~~~ !!! error TS2303: Circular definition of import alias 'Foo'. @@ -17,4 +18,6 @@ declarationEmitUnknownImport2.ts(1,17): error TS1005: ';' expected. !!! error TS2503: Cannot find namespace 'From'. ~~~~~~~ !!! error TS1005: ';' expected. - export default Foo \ No newline at end of file + export default Foo + ~~~~~~~~~~~~~~~~~~ +!!! error TS2303: Circular definition of import alias 'Foo'. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport2.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport2.errors.txt.diff new file mode 100644 index 0000000000..00d4874583 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport2.errors.txt.diff @@ -0,0 +1,22 @@ +--- old.declarationEmitUnknownImport2.errors.txt ++++ new.declarationEmitUnknownImport2.errors.txt +@@= skipped -2, +2 lines =@@ + declarationEmitUnknownImport2.ts(1,12): error TS2304: Cannot find name 'From'. + declarationEmitUnknownImport2.ts(1,12): error TS2503: Cannot find namespace 'From'. + declarationEmitUnknownImport2.ts(1,17): error TS1005: ';' expected. +- +- +-==== declarationEmitUnknownImport2.ts (5 errors) ==== ++declarationEmitUnknownImport2.ts(2,1): error TS2303: Circular definition of import alias 'Foo'. ++ ++ ++==== declarationEmitUnknownImport2.ts (6 errors) ==== + import Foo From './Foo'; // Syntax error + ~~~~~~~~~~~~~~~ + !!! error TS2303: Circular definition of import alias 'Foo'. +@@= skipped -15, +16 lines =@@ + ~~~~~~~ + !!! error TS1005: ';' expected. + export default Foo ++ ~~~~~~~~~~~~~~~~~~ ++!!! error TS2303: Circular definition of import alias 'Foo'. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/exportAsNamespaceConflict.errors.txt b/testdata/baselines/reference/submodule/compiler/exportAsNamespaceConflict.errors.txt index 4b08c73606..0008df30a8 100644 --- a/testdata/baselines/reference/submodule/compiler/exportAsNamespaceConflict.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/exportAsNamespaceConflict.errors.txt @@ -1,9 +1,12 @@ +/a.d.ts(2,1): error TS2303: Circular definition of import alias 'N'. /a.d.ts(3,1): error TS2303: Circular definition of import alias 'N'. -==== /a.d.ts (1 errors) ==== +==== /a.d.ts (2 errors) ==== declare global { namespace N {} } export = N; + ~~~~~~~~~~~ +!!! error TS2303: Circular definition of import alias 'N'. export as namespace N; ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2303: Circular definition of import alias 'N'. diff --git a/testdata/baselines/reference/submodule/compiler/exportAsNamespaceConflict.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/exportAsNamespaceConflict.errors.txt.diff new file mode 100644 index 0000000000..78e304bf69 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/exportAsNamespaceConflict.errors.txt.diff @@ -0,0 +1,16 @@ +--- old.exportAsNamespaceConflict.errors.txt ++++ new.exportAsNamespaceConflict.errors.txt +@@= skipped -0, +0 lines =@@ ++/a.d.ts(2,1): error TS2303: Circular definition of import alias 'N'. + /a.d.ts(3,1): error TS2303: Circular definition of import alias 'N'. + + +-==== /a.d.ts (1 errors) ==== ++==== /a.d.ts (2 errors) ==== + declare global { namespace N {} } + export = N; ++ ~~~~~~~~~~~ ++!!! error TS2303: Circular definition of import alias 'N'. + export as namespace N; + ~~~~~~~~~~~~~~~~~~~~~~ + !!! error TS2303: Circular definition of import alias 'N'. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/circular1.errors.txt b/testdata/baselines/reference/submodule/conformance/circular1.errors.txt index eb583371b3..fe5a13637d 100644 --- a/testdata/baselines/reference/submodule/conformance/circular1.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/circular1.errors.txt @@ -1,8 +1,11 @@ +/a.ts(1,15): error TS2303: Circular definition of import alias 'A'. /b.ts(1,15): error TS2303: Circular definition of import alias 'A'. -==== /a.ts (0 errors) ==== +==== /a.ts (1 errors) ==== export type { A } from './b'; + ~ +!!! error TS2303: Circular definition of import alias 'A'. ==== /b.ts (1 errors) ==== export type { A } from './a'; diff --git a/testdata/baselines/reference/submodule/conformance/circular1.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/circular1.errors.txt.diff new file mode 100644 index 0000000000..6927a68e06 --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/circular1.errors.txt.diff @@ -0,0 +1,15 @@ +--- old.circular1.errors.txt ++++ new.circular1.errors.txt +@@= skipped -0, +0 lines =@@ ++/a.ts(1,15): error TS2303: Circular definition of import alias 'A'. + /b.ts(1,15): error TS2303: Circular definition of import alias 'A'. + + +-==== /a.ts (0 errors) ==== ++==== /a.ts (1 errors) ==== + export type { A } from './b'; ++ ~ ++!!! error TS2303: Circular definition of import alias 'A'. + + ==== /b.ts (1 errors) ==== + export type { A } from './a'; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/circular3.errors.txt b/testdata/baselines/reference/submodule/conformance/circular3.errors.txt index 5da3e36406..0c72601d6c 100644 --- a/testdata/baselines/reference/submodule/conformance/circular3.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/circular3.errors.txt @@ -1,13 +1,22 @@ +/a.ts(1,15): error TS2303: Circular definition of import alias 'A'. +/a.ts(2,15): error TS2303: Circular definition of import alias 'B'. /b.ts(1,15): error TS2303: Circular definition of import alias 'B'. +/b.ts(2,15): error TS2303: Circular definition of import alias 'A'. -==== /a.ts (0 errors) ==== +==== /a.ts (2 errors) ==== import type { A } from './b'; + ~ +!!! error TS2303: Circular definition of import alias 'A'. export type { A as B }; + ~~~~~~ +!!! error TS2303: Circular definition of import alias 'B'. -==== /b.ts (1 errors) ==== +==== /b.ts (2 errors) ==== import type { B } from './a'; ~ !!! error TS2303: Circular definition of import alias 'B'. export type { B as A }; + ~~~~~~ +!!! error TS2303: Circular definition of import alias 'A'. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/circular3.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/circular3.errors.txt.diff new file mode 100644 index 0000000000..c217af658d --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/circular3.errors.txt.diff @@ -0,0 +1,29 @@ +--- old.circular3.errors.txt ++++ new.circular3.errors.txt +@@= skipped -0, +0 lines =@@ ++/a.ts(1,15): error TS2303: Circular definition of import alias 'A'. ++/a.ts(2,15): error TS2303: Circular definition of import alias 'B'. + /b.ts(1,15): error TS2303: Circular definition of import alias 'B'. +- +- +-==== /a.ts (0 errors) ==== ++/b.ts(2,15): error TS2303: Circular definition of import alias 'A'. ++ ++ ++==== /a.ts (2 errors) ==== + import type { A } from './b'; ++ ~ ++!!! error TS2303: Circular definition of import alias 'A'. + export type { A as B }; ++ ~~~~~~ ++!!! error TS2303: Circular definition of import alias 'B'. + +-==== /b.ts (1 errors) ==== ++==== /b.ts (2 errors) ==== + import type { B } from './a'; + ~ + !!! error TS2303: Circular definition of import alias 'B'. + export type { B as A }; ++ ~~~~~~ ++!!! error TS2303: Circular definition of import alias 'A'. + \ No newline at end of file From a586c9521fb82782e5f0af3133a585b4808dd592 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 19 Oct 2025 11:19:29 -0400 Subject: [PATCH 3/3] Stop skipping circularity tests when checking concurrently --- internal/testrunner/compiler_runner.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/testrunner/compiler_runner.go b/internal/testrunner/compiler_runner.go index 215cba9c8d..35eac30102 100644 --- a/internal/testrunner/compiler_runner.go +++ b/internal/testrunner/compiler_runner.go @@ -337,11 +337,6 @@ func newCompilerTest( } var concurrentSkippedErrorBaselines = map[string]string{ - "circular1.ts": "Circular error reported in an extra position.", - "circular3.ts": "Circular error reported in an extra position.", - "recursiveExportAssignmentAndFindAliasedType1.ts": "Circular error reported in an extra position.", - "recursiveExportAssignmentAndFindAliasedType2.ts": "Circular error reported in an extra position.", - "recursiveExportAssignmentAndFindAliasedType3.ts": "Circular error reported in an extra position.", "typeOnlyMerge2.ts": "Type-only merging is not detected when files are checked on different checkers.", "typeOnlyMerge3.ts": "Type-only merging is not detected when files are checked on different checkers.", }