From 94d6b4507e50b2bc23a5539c2fb23ee0bd2b73c2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 20 Jul 2020 20:35:47 -0700 Subject: [PATCH] Consistent errors on circular base types (#39675) * Properly track and report errors on circular base types * Accept new baselines * Add regression test --- src/compiler/checker.ts | 43 +++++++++++++------ src/compiler/types.ts | 2 + .../reference/circularBaseTypes.errors.txt | 19 ++++++++ .../baselines/reference/circularBaseTypes.js | 29 +++++++++++++ .../reference/circularBaseTypes.symbols | 28 ++++++++++++ .../reference/circularBaseTypes.types | 21 +++++++++ ...onstraintYieldsAppropriateError.errors.txt | 23 ++++++++++ .../interfaceDeclaration1.errors.txt | 5 ++- ...hatIndirectlyInheritsFromItself.errors.txt | 14 +++++- .../reference/recursiveBaseCheck5.errors.txt | 5 ++- .../reference/recursiveInheritance.errors.txt | 5 ++- tests/cases/compiler/circularBaseTypes.ts | 12 ++++++ 12 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 tests/baselines/reference/circularBaseTypes.errors.txt create mode 100644 tests/baselines/reference/circularBaseTypes.js create mode 100644 tests/baselines/reference/circularBaseTypes.symbols create mode 100644 tests/baselines/reference/circularBaseTypes.types create mode 100644 tests/baselines/reference/circularConstraintYieldsAppropriateError.errors.txt create mode 100644 tests/cases/compiler/circularBaseTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b962dfd0c6d..d20e460f37b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -166,6 +166,7 @@ namespace ts { ImmediateBaseConstraint, EnumTagType, ResolvedTypeArguments, + ResolvedBaseTypes, } const enum CheckMode { @@ -7491,6 +7492,8 @@ namespace ts { return !!(target).immediateBaseConstraint; case TypeSystemPropertyName.ResolvedTypeArguments: return !!(target as TypeReference).resolvedTypeArguments; + case TypeSystemPropertyName.ResolvedBaseTypes: + return !!(target as InterfaceType).baseTypesResolved; } return Debug.assertNever(propertyName); } @@ -8917,22 +8920,36 @@ namespace ts { return resolvedImplementsTypes; } + function reportCircularBaseType(node: Node, type: Type) { + error(node, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + } + function getBaseTypes(type: InterfaceType): BaseType[] { - if (!type.resolvedBaseTypes) { - if (type.objectFlags & ObjectFlags.Tuple) { - type.resolvedBaseTypes = [getTupleBaseType(type)]; - } - else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - if (type.symbol.flags & SymbolFlags.Class) { - resolveBaseTypesOfClass(type); + if (!type.baseTypesResolved) { + if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) { + if (type.objectFlags & ObjectFlags.Tuple) { + type.resolvedBaseTypes = [getTupleBaseType(type)]; } - if (type.symbol.flags & SymbolFlags.Interface) { - resolveBaseTypesOfInterface(type); + else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + if (type.symbol.flags & SymbolFlags.Class) { + resolveBaseTypesOfClass(type); + } + if (type.symbol.flags & SymbolFlags.Interface) { + resolveBaseTypesOfInterface(type); + } + } + else { + Debug.fail("type must be class or interface"); + } + if (!popTypeResolution()) { + for (const declaration of type.symbol.declarations) { + if (declaration.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.InterfaceDeclaration) { + reportCircularBaseType(declaration, type); + } + } } } - else { - Debug.fail("type must be class or interface"); - } + type.baseTypesResolved = true; } return type.resolvedBaseTypes; } @@ -9041,7 +9058,7 @@ namespace ts { } } else { - error(declaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + reportCircularBaseType(declaration, type); } } else { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 67065ca8046..8f9445cd951 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5050,6 +5050,8 @@ namespace ts { resolvedBaseConstructorType?: Type; // Resolved base constructor type of class /* @internal */ resolvedBaseTypes: BaseType[]; // Resolved base types + /* @internal */ + baseTypesResolved?: boolean; } // Object type or intersection of object types diff --git a/tests/baselines/reference/circularBaseTypes.errors.txt b/tests/baselines/reference/circularBaseTypes.errors.txt new file mode 100644 index 00000000000..e8a4f4175ae --- /dev/null +++ b/tests/baselines/reference/circularBaseTypes.errors.txt @@ -0,0 +1,19 @@ +tests/cases/compiler/circularBaseTypes.ts(4,11): error TS2310: Type 'M2' recursively references itself as a base type. +tests/cases/compiler/circularBaseTypes.ts(5,6): error TS2456: Type alias 'M3' circularly references itself. + + +==== tests/cases/compiler/circularBaseTypes.ts (2 errors) ==== + // Repro from #38098 + + type M = { value: T }; + interface M2 extends M {}; // Error + ~~ +!!! error TS2310: Type 'M2' recursively references itself as a base type. + type M3 = M2[keyof M2]; // Error + ~~ +!!! error TS2456: Type alias 'M3' circularly references itself. + + function f(m: M3) { + return m.value; + } + \ No newline at end of file diff --git a/tests/baselines/reference/circularBaseTypes.js b/tests/baselines/reference/circularBaseTypes.js new file mode 100644 index 00000000000..1c5f77c1b2e --- /dev/null +++ b/tests/baselines/reference/circularBaseTypes.js @@ -0,0 +1,29 @@ +//// [circularBaseTypes.ts] +// Repro from #38098 + +type M = { value: T }; +interface M2 extends M {}; // Error +type M3 = M2[keyof M2]; // Error + +function f(m: M3) { + return m.value; +} + + +//// [circularBaseTypes.js] +"use strict"; +// Repro from #38098 +; // Error +function f(m) { + return m.value; +} + + +//// [circularBaseTypes.d.ts] +declare type M = { + value: T; +}; +interface M2 extends M { +} +declare type M3 = M2[keyof M2]; +declare function f(m: M3): any; diff --git a/tests/baselines/reference/circularBaseTypes.symbols b/tests/baselines/reference/circularBaseTypes.symbols new file mode 100644 index 00000000000..9acd4d6928b --- /dev/null +++ b/tests/baselines/reference/circularBaseTypes.symbols @@ -0,0 +1,28 @@ +=== tests/cases/compiler/circularBaseTypes.ts === +// Repro from #38098 + +type M = { value: T }; +>M : Symbol(M, Decl(circularBaseTypes.ts, 0, 0)) +>T : Symbol(T, Decl(circularBaseTypes.ts, 2, 7)) +>value : Symbol(value, Decl(circularBaseTypes.ts, 2, 13)) +>T : Symbol(T, Decl(circularBaseTypes.ts, 2, 7)) + +interface M2 extends M {}; // Error +>M2 : Symbol(M2, Decl(circularBaseTypes.ts, 2, 25)) +>M : Symbol(M, Decl(circularBaseTypes.ts, 0, 0)) +>M3 : Symbol(M3, Decl(circularBaseTypes.ts, 3, 30)) + +type M3 = M2[keyof M2]; // Error +>M3 : Symbol(M3, Decl(circularBaseTypes.ts, 3, 30)) +>M2 : Symbol(M2, Decl(circularBaseTypes.ts, 2, 25)) +>M2 : Symbol(M2, Decl(circularBaseTypes.ts, 2, 25)) + +function f(m: M3) { +>f : Symbol(f, Decl(circularBaseTypes.ts, 4, 23)) +>m : Symbol(m, Decl(circularBaseTypes.ts, 6, 11)) +>M3 : Symbol(M3, Decl(circularBaseTypes.ts, 3, 30)) + + return m.value; +>m : Symbol(m, Decl(circularBaseTypes.ts, 6, 11)) +} + diff --git a/tests/baselines/reference/circularBaseTypes.types b/tests/baselines/reference/circularBaseTypes.types new file mode 100644 index 00000000000..59dce43d19e --- /dev/null +++ b/tests/baselines/reference/circularBaseTypes.types @@ -0,0 +1,21 @@ +=== tests/cases/compiler/circularBaseTypes.ts === +// Repro from #38098 + +type M = { value: T }; +>M : M +>value : T + +interface M2 extends M {}; // Error +type M3 = M2[keyof M2]; // Error +>M3 : any + +function f(m: M3) { +>f : (m: any) => any +>m : any + + return m.value; +>m.value : any +>m : any +>value : any +} + diff --git a/tests/baselines/reference/circularConstraintYieldsAppropriateError.errors.txt b/tests/baselines/reference/circularConstraintYieldsAppropriateError.errors.txt new file mode 100644 index 00000000000..709b3957561 --- /dev/null +++ b/tests/baselines/reference/circularConstraintYieldsAppropriateError.errors.txt @@ -0,0 +1,23 @@ +tests/cases/compiler/circularConstraintYieldsAppropriateError.ts(10,7): error TS2310: Type 'Foo' recursively references itself as a base type. + + +==== tests/cases/compiler/circularConstraintYieldsAppropriateError.ts (1 errors) ==== + // https://github.com/Microsoft/TypeScript/issues/16861 + class BaseType { + bar: T + } + + class NextType extends BaseType { + baz: string; + } + + class Foo extends NextType { + ~~~ +!!! error TS2310: Type 'Foo' recursively references itself as a base type. + someProp: { + test: true + } + } + + const foo = new Foo(); + foo.bar.test \ No newline at end of file diff --git a/tests/baselines/reference/interfaceDeclaration1.errors.txt b/tests/baselines/reference/interfaceDeclaration1.errors.txt index b4c41ca350e..bed7e796edf 100644 --- a/tests/baselines/reference/interfaceDeclaration1.errors.txt +++ b/tests/baselines/reference/interfaceDeclaration1.errors.txt @@ -7,11 +7,12 @@ tests/cases/compiler/interfaceDeclaration1.ts(22,11): error TS2310: Type 'I5' re tests/cases/compiler/interfaceDeclaration1.ts(35,7): error TS2420: Class 'C1' incorrectly implements interface 'I3'. Property 'prototype' is missing in type 'C1' but required in type 'I3'. tests/cases/compiler/interfaceDeclaration1.ts(41,11): error TS2310: Type 'i8' recursively references itself as a base type. +tests/cases/compiler/interfaceDeclaration1.ts(42,11): error TS2310: Type 'i9' recursively references itself as a base type. tests/cases/compiler/interfaceDeclaration1.ts(52,11): error TS2320: Interface 'i12' cannot simultaneously extend types 'i10' and 'i11'. Named property 'foo' of types 'i10' and 'i11' are not identical. -==== tests/cases/compiler/interfaceDeclaration1.ts (9 errors) ==== +==== tests/cases/compiler/interfaceDeclaration1.ts (10 errors) ==== interface I1 { item:number; ~~~~ @@ -73,6 +74,8 @@ tests/cases/compiler/interfaceDeclaration1.ts(52,11): error TS2320: Interface 'i ~~ !!! error TS2310: Type 'i8' recursively references itself as a base type. interface i9 extends i8 { } + ~~ +!!! error TS2310: Type 'i9' recursively references itself as a base type. interface i10 { foo():number; diff --git a/tests/baselines/reference/interfaceThatIndirectlyInheritsFromItself.errors.txt b/tests/baselines/reference/interfaceThatIndirectlyInheritsFromItself.errors.txt index 3749cae8682..9cb85f5f13b 100644 --- a/tests/baselines/reference/interfaceThatIndirectlyInheritsFromItself.errors.txt +++ b/tests/baselines/reference/interfaceThatIndirectlyInheritsFromItself.errors.txt @@ -1,8 +1,12 @@ tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(1,11): error TS2310: Type 'Base' recursively references itself as a base type. +tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(5,11): error TS2310: Type 'Derived' recursively references itself as a base type. +tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(9,11): error TS2310: Type 'Derived2' recursively references itself as a base type. tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(14,15): error TS2310: Type 'Base' recursively references itself as a base type. +tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(18,15): error TS2310: Type 'Derived' recursively references itself as a base type. +tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts(22,15): error TS2310: Type 'Derived2' recursively references itself as a base type. -==== tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts (2 errors) ==== +==== tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts (6 errors) ==== interface Base extends Derived2 { // error ~~~~ !!! error TS2310: Type 'Base' recursively references itself as a base type. @@ -10,10 +14,14 @@ tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectly } interface Derived extends Base { + ~~~~~~~ +!!! error TS2310: Type 'Derived' recursively references itself as a base type. y: string; } interface Derived2 extends Derived { + ~~~~~~~~ +!!! error TS2310: Type 'Derived2' recursively references itself as a base type. z: string; } @@ -25,10 +33,14 @@ tests/cases/conformance/interfaces/interfaceDeclarations/interfaceThatIndirectly } interface Derived extends Base { + ~~~~~~~ +!!! error TS2310: Type 'Derived' recursively references itself as a base type. y: string; } interface Derived2 extends Derived { + ~~~~~~~~ +!!! error TS2310: Type 'Derived2' recursively references itself as a base type. z: string; } } \ No newline at end of file diff --git a/tests/baselines/reference/recursiveBaseCheck5.errors.txt b/tests/baselines/reference/recursiveBaseCheck5.errors.txt index 89c857b741f..f655230ad96 100644 --- a/tests/baselines/reference/recursiveBaseCheck5.errors.txt +++ b/tests/baselines/reference/recursiveBaseCheck5.errors.txt @@ -1,12 +1,15 @@ tests/cases/compiler/recursiveBaseCheck5.ts(1,11): error TS2310: Type 'I1' recursively references itself as a base type. +tests/cases/compiler/recursiveBaseCheck5.ts(2,11): error TS2310: Type 'I2' recursively references itself as a base type. tests/cases/compiler/recursiveBaseCheck5.ts(4,9): error TS2339: Property 'blah' does not exist on type 'X'. -==== tests/cases/compiler/recursiveBaseCheck5.ts (2 errors) ==== +==== tests/cases/compiler/recursiveBaseCheck5.ts (3 errors) ==== interface I1 extends I2 { } ~~ !!! error TS2310: Type 'I1' recursively references itself as a base type. interface I2 extends I1 { } + ~~ +!!! error TS2310: Type 'I2' recursively references itself as a base type. class X implements I2 { } (new X).blah; ~~~~ diff --git a/tests/baselines/reference/recursiveInheritance.errors.txt b/tests/baselines/reference/recursiveInheritance.errors.txt index 2758d08a297..599467091e1 100644 --- a/tests/baselines/reference/recursiveInheritance.errors.txt +++ b/tests/baselines/reference/recursiveInheritance.errors.txt @@ -1,8 +1,9 @@ tests/cases/compiler/recursiveInheritance.ts(1,11): error TS2310: Type 'I5' recursively references itself as a base type. tests/cases/compiler/recursiveInheritance.ts(5,11): error TS2310: Type 'i8' recursively references itself as a base type. +tests/cases/compiler/recursiveInheritance.ts(6,11): error TS2310: Type 'i9' recursively references itself as a base type. -==== tests/cases/compiler/recursiveInheritance.ts (2 errors) ==== +==== tests/cases/compiler/recursiveInheritance.ts (3 errors) ==== interface I5 extends I5 { // error ~~ !!! 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 ~~ !!! error TS2310: Type 'i8' recursively references itself as a base type. interface i9 extends i8 { } // error + ~~ +!!! error TS2310: Type 'i9' recursively references itself as a base type. \ No newline at end of file diff --git a/tests/cases/compiler/circularBaseTypes.ts b/tests/cases/compiler/circularBaseTypes.ts new file mode 100644 index 00000000000..d858b2ebfc0 --- /dev/null +++ b/tests/cases/compiler/circularBaseTypes.ts @@ -0,0 +1,12 @@ +// @strict: true +// @declaration: true + +// Repro from #38098 + +type M = { value: T }; +interface M2 extends M {}; // Error +type M3 = M2[keyof M2]; // Error + +function f(m: M3) { + return m.value; +}