Skip to content

Commit 94d6b45

Browse files
authored
Consistent errors on circular base types (#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy