Skip to content

Commit 94d6b45

Browse files
authored
Consistent errors on circular base types (microsoft#39675)
* Properly track and report errors on circular base types * Accept new baselines * Add regression test
1 parent 5ae4b5d commit 94d6b45

12 files changed

+189
-17
lines changed

src/compiler/checker.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ namespace ts {
166166
ImmediateBaseConstraint,
167167
EnumTagType,
168168
ResolvedTypeArguments,
169+
ResolvedBaseTypes,
169170
}
170171

171172
const enum CheckMode {
@@ -7491,6 +7492,8 @@ namespace ts {
74917492
return !!(<Type>target).immediateBaseConstraint;
74927493
case TypeSystemPropertyName.ResolvedTypeArguments:
74937494
return !!(target as TypeReference).resolvedTypeArguments;
7495+
case TypeSystemPropertyName.ResolvedBaseTypes:
7496+
return !!(target as InterfaceType).baseTypesResolved;
74947497
}
74957498
return Debug.assertNever(propertyName);
74967499
}
@@ -8917,22 +8920,36 @@ namespace ts {
89178920
return resolvedImplementsTypes;
89188921
}
89198922

8923+
function reportCircularBaseType(node: Node, type: Type) {
8924+
error(node, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType));
8925+
}
8926+
89208927
function getBaseTypes(type: InterfaceType): BaseType[] {
8921-
if (!type.resolvedBaseTypes) {
8922-
if (type.objectFlags & ObjectFlags.Tuple) {
8923-
type.resolvedBaseTypes = [getTupleBaseType(<TupleType>type)];
8924-
}
8925-
else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
8926-
if (type.symbol.flags & SymbolFlags.Class) {
8927-
resolveBaseTypesOfClass(type);
8928+
if (!type.baseTypesResolved) {
8929+
if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) {
8930+
if (type.objectFlags & ObjectFlags.Tuple) {
8931+
type.resolvedBaseTypes = [getTupleBaseType(<TupleType>type)];
89288932
}
8929-
if (type.symbol.flags & SymbolFlags.Interface) {
8930-
resolveBaseTypesOfInterface(type);
8933+
else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
8934+
if (type.symbol.flags & SymbolFlags.Class) {
8935+
resolveBaseTypesOfClass(type);
8936+
}
8937+
if (type.symbol.flags & SymbolFlags.Interface) {
8938+
resolveBaseTypesOfInterface(type);
8939+
}
8940+
}
8941+
else {
8942+
Debug.fail("type must be class or interface");
8943+
}
8944+
if (!popTypeResolution()) {
8945+
for (const declaration of type.symbol.declarations) {
8946+
if (declaration.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.InterfaceDeclaration) {
8947+
reportCircularBaseType(declaration, type);
8948+
}
8949+
}
89318950
}
89328951
}
8933-
else {
8934-
Debug.fail("type must be class or interface");
8935-
}
8952+
type.baseTypesResolved = true;
89368953
}
89378954
return type.resolvedBaseTypes;
89388955
}
@@ -9041,7 +9058,7 @@ namespace ts {
90419058
}
90429059
}
90439060
else {
9044-
error(declaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType));
9061+
reportCircularBaseType(declaration, type);
90459062
}
90469063
}
90479064
else {

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5050,6 +5050,8 @@ namespace ts {
50505050
resolvedBaseConstructorType?: Type; // Resolved base constructor type of class
50515051
/* @internal */
50525052
resolvedBaseTypes: BaseType[]; // Resolved base types
5053+
/* @internal */
5054+
baseTypesResolved?: boolean;
50535055
}
50545056

50555057
// Object type or intersection of object types
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
tests/cases/compiler/circularBaseTypes.ts(4,11): error TS2310: Type 'M2' recursively references itself as a base type.
2+
tests/cases/compiler/circularBaseTypes.ts(5,6): error TS2456: Type alias 'M3' circularly references itself.
3+
4+
5+
==== tests/cases/compiler/circularBaseTypes.ts (2 errors) ====
6+
// Repro from #38098
7+
8+
type M<T> = { value: T };
9+
interface M2 extends M<M3> {}; // Error
10+
~~
11+
!!! error TS2310: Type 'M2' recursively references itself as a base type.
12+
type M3 = M2[keyof M2]; // Error
13+
~~
14+
!!! error TS2456: Type alias 'M3' circularly references itself.
15+
16+
function f(m: M3) {
17+
return m.value;
18+
}
19+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//// [circularBaseTypes.ts]
2+
// Repro from #38098
3+
4+
type M<T> = { value: T };
5+
interface M2 extends M<M3> {}; // Error
6+
type M3 = M2[keyof M2]; // Error
7+
8+
function f(m: M3) {
9+
return m.value;
10+
}
11+
12+
13+
//// [circularBaseTypes.js]
14+
"use strict";
15+
// Repro from #38098
16+
; // Error
17+
function f(m) {
18+
return m.value;
19+
}
20+
21+
22+
//// [circularBaseTypes.d.ts]
23+
declare type M<T> = {
24+
value: T;
25+
};
26+
interface M2 extends M<M3> {
27+
}
28+
declare type M3 = M2[keyof M2];
29+
declare function f(m: M3): any;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
=== tests/cases/compiler/circularBaseTypes.ts ===
2+
// Repro from #38098
3+
4+
type M<T> = { value: T };
5+
>M : Symbol(M, Decl(circularBaseTypes.ts, 0, 0))
6+
>T : Symbol(T, Decl(circularBaseTypes.ts, 2, 7))
7+
>value : Symbol(value, Decl(circularBaseTypes.ts, 2, 13))
8+
>T : Symbol(T, Decl(circularBaseTypes.ts, 2, 7))
9+
10+
interface M2 extends M<M3> {}; // Error
11+
>M2 : Symbol(M2, Decl(circularBaseTypes.ts, 2, 25))
12+
>M : Symbol(M, Decl(circularBaseTypes.ts, 0, 0))
13+
>M3 : Symbol(M3, Decl(circularBaseTypes.ts, 3, 30))
14+
15+
type M3 = M2[keyof M2]; // Error
16+
>M3 : Symbol(M3, Decl(circularBaseTypes.ts, 3, 30))
17+
>M2 : Symbol(M2, Decl(circularBaseTypes.ts, 2, 25))
18+
>M2 : Symbol(M2, Decl(circularBaseTypes.ts, 2, 25))
19+
20+
function f(m: M3) {
21+
>f : Symbol(f, Decl(circularBaseTypes.ts, 4, 23))
22+
>m : Symbol(m, Decl(circularBaseTypes.ts, 6, 11))
23+
>M3 : Symbol(M3, Decl(circularBaseTypes.ts, 3, 30))
24+
25+
return m.value;
26+
>m : Symbol(m, Decl(circularBaseTypes.ts, 6, 11))
27+
}
28+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/compiler/circularBaseTypes.ts ===
2+
// Repro from #38098
3+
4+
type M<T> = { value: T };
5+
>M : M<T>
6+
>value : T
7+
8+
interface M2 extends M<M3> {}; // Error
9+
type M3 = M2[keyof M2]; // Error
10+
>M3 : any
11+
12+
function f(m: M3) {
13+
>f : (m: any) => any
14+
>m : any
15+
16+
return m.value;
17+
>m.value : any
18+
>m : any
19+
>value : any
20+
}
21+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
tests/cases/compiler/circularConstraintYieldsAppropriateError.ts(10,7): error TS2310: Type 'Foo' recursively references itself as a base type.
2+
3+
4+
==== tests/cases/compiler/circularConstraintYieldsAppropriateError.ts (1 errors) ====
5+
// https://github.com/Microsoft/TypeScript/issues/16861
6+
class BaseType<T> {
7+
bar: T
8+
}
9+
10+
class NextType<C extends { someProp: any }, T = C['someProp']> extends BaseType<T> {
11+
baz: string;
12+
}
13+
14+
class Foo extends NextType<Foo> {
15+
~~~
16+
!!! error TS2310: Type 'Foo' recursively references itself as a base type.
17+
someProp: {
18+
test: true
19+
}
20+
}
21+
22+
const foo = new Foo();
23+
foo.bar.test

tests/baselines/reference/interfaceDeclaration1.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ tests/cases/compiler/interfaceDeclaration1.ts(22,11): error TS2310: Type 'I5' re
77
tests/cases/compiler/interfaceDeclaration1.ts(35,7): error TS2420: Class 'C1' incorrectly implements interface 'I3'.
88
Property 'prototype' is missing in type 'C1' but required in type 'I3'.
99
tests/cases/compiler/interfaceDeclaration1.ts(41,11): error TS2310: Type 'i8' recursively references itself as a base type.
10+
tests/cases/compiler/interfaceDeclaration1.ts(42,11): error TS2310: Type 'i9' recursively references itself as a base type.
1011
tests/cases/compiler/interfaceDeclaration1.ts(52,11): error TS2320: Interface 'i12' cannot simultaneously extend types 'i10' and 'i11'.
1112
Named property 'foo' of types 'i10' and 'i11' are not identical.
1213

1314

14-
==== tests/cases/compiler/interfaceDeclaration1.ts (9 errors) ====
15+
==== tests/cases/compiler/interfaceDeclaration1.ts (10 errors) ====
1516
interface I1 {
1617
item:number;
1718
~~~~
@@ -73,6 +74,8 @@ tests/cases/compiler/interfaceDeclaration1.ts(52,11): error TS2320: Interface 'i
7374
~~
7475
!!! error TS2310: Type 'i8' recursively references itself as a base type.
7576
interface i9 extends i8 { }
77+
~~
78+
!!! error TS2310: Type 'i9' recursively references itself as a base type.
7679

7780
interface i10 {
7881
foo():number;

tests/baselines/reference/interfaceThatIndirectlyInheritsFromItself.errors.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(1,11): error TS2310: Type 'Base' recursively references itself as a base type.
2+
tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(5,11): error TS2310: Type 'Derived' recursively references itself as a base type.
3+
tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(9,11): error TS2310: Type 'Derived2' recursively references itself as a base type.
24
tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(14,15): error TS2310: Type 'Base<T>' recursively references itself as a base type.
5+
tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(18,15): error TS2310: Type 'Derived<T>' recursively references itself as a base type.
6+
tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(22,15): error TS2310: Type 'Derived2<T>' recursively references itself as a base type.
37

48

5-
==== tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts (2 errors) ====
9+
==== tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts (6 errors) ====
610
interface Base extends Derived2 { // error
711
~~~~
812
!!! error TS2310: Type 'Base' recursively references itself as a base type.
913
x: string;
1014
}
1115

1216
interface Derived extends Base {
17+
~~~~~~~
18+
!!! error TS2310: Type 'Derived' recursively references itself as a base type.
1319
y: string;
1420
}
1521

1622
interface Derived2 extends Derived {
23+
~~~~~~~~
24+
!!! error TS2310: Type 'Derived2' recursively references itself as a base type.
1725
z: string;
1826
}
1927

@@ -25,10 +33,14 @@ tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectly
2533
}
2634

2735
interface Derived<T> extends Base<T> {
36+
~~~~~~~
37+
!!! error TS2310: Type 'Derived<T>' recursively references itself as a base type.
2838
y: string;
2939
}
3040

3141
interface Derived2<T> extends Derived<T> {
42+
~~~~~~~~
43+
!!! error TS2310: Type 'Derived2<T>' recursively references itself as a base type.
3244
z: string;
3345
}
3446
}

tests/baselines/reference/recursiveBaseCheck5.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
tests/cases/compiler/recursiveBaseCheck5.ts(1,11): error TS2310: Type 'I1<T>' recursively references itself as a base type.
2+
tests/cases/compiler/recursiveBaseCheck5.ts(2,11): error TS2310: Type 'I2<T>' recursively references itself as a base type.
23
tests/cases/compiler/recursiveBaseCheck5.ts(4,9): error TS2339: Property 'blah' does not exist on type 'X<unknown, unknown>'.
34

45

5-
==== tests/cases/compiler/recursiveBaseCheck5.ts (2 errors) ====
6+
==== tests/cases/compiler/recursiveBaseCheck5.ts (3 errors) ====
67
interface I1<T> extends I2<string> { }
78
~~
89
!!! error TS2310: Type 'I1<T>' recursively references itself as a base type.
910
interface I2<T> extends I1<T> { }
11+
~~
12+
!!! error TS2310: Type 'I2<T>' recursively references itself as a base type.
1013
class X<T, U> implements I2<T> { }
1114
(new X).blah;
1215
~~~~

tests/baselines/reference/recursiveInheritance.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
tests/cases/compiler/recursiveInheritance.ts(1,11): error TS2310: Type 'I5' recursively references itself as a base type.
22
tests/cases/compiler/recursiveInheritance.ts(5,11): error TS2310: Type 'i8' recursively references itself as a base type.
3+
tests/cases/compiler/recursiveInheritance.ts(6,11): error TS2310: Type 'i9' recursively references itself as a base type.
34

45

5-
==== tests/cases/compiler/recursiveInheritance.ts (2 errors) ====
6+
==== tests/cases/compiler/recursiveInheritance.ts (3 errors) ====
67
interface I5 extends I5 { // error
78
~~
89
!!! error TS2310: Type 'I5' recursively references itself as a base type.
@@ -13,4 +14,6 @@ tests/cases/compiler/recursiveInheritance.ts(5,11): error TS2310: Type 'i8' recu
1314
~~
1415
!!! error TS2310: Type 'i8' recursively references itself as a base type.
1516
interface i9 extends i8 { } // error
17+
~~
18+
!!! error TS2310: Type 'i9' recursively references itself as a base type.
1619

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @strict: true
2+
// @declaration: true
3+
4+
// Repro from #38098
5+
6+
type M<T> = { value: T };
7+
interface M2 extends M<M3> {}; // Error
8+
type M3 = M2[keyof M2]; // Error
9+
10+
function f(m: M3) {
11+
return m.value;
12+
}

0 commit comments

Comments
 (0)