diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 87d95b64d31..a4cfac416fc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5123,11 +5123,11 @@ namespace ts { return type.resolvedBaseTypes; } - function resolveBaseTypesOfClass(type: InterfaceType): void { - type.resolvedBaseTypes = emptyArray; + function resolveBaseTypesOfClass(type: InterfaceType) { + type.resolvedBaseTypes = resolvingEmptyArray; const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { - return; + return type.resolvedBaseTypes = emptyArray; } const baseTypeNode = getBaseTypeNodeOfClass(type); const typeArgs = typeArgumentsFromTypeReferenceNode(baseTypeNode); @@ -5150,24 +5150,31 @@ namespace ts { const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode); if (!constructors.length) { error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments); - return; + return type.resolvedBaseTypes = emptyArray; } baseType = getReturnTypeOfSignature(constructors[0]); } if (baseType === unknownType) { - return; + return type.resolvedBaseTypes = emptyArray; } if (!isValidBaseType(baseType)) { error(baseTypeNode.expression, Diagnostics.Base_constructor_return_type_0_is_not_a_class_or_interface_type, typeToString(baseType)); - return; + return type.resolvedBaseTypes = emptyArray; } if (type === baseType || hasBaseType(baseType, type)) { error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); - return; + return type.resolvedBaseTypes = emptyArray; } - type.resolvedBaseTypes = [baseType]; + if (type.resolvedBaseTypes === resolvingEmptyArray) { + // Circular reference, likely through instantiation of default parameters + // (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset + // as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a + // partial instantiation of the members without the base types fully resolved + (type as Type as ResolvedType).members = undefined; + } + return type.resolvedBaseTypes = [baseType]; } function areAllOuterTypeParametersApplied(type: Type): boolean { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4a3816f04d2..8a3e3132315 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3,6 +3,7 @@ /* @internal */ namespace ts { export const emptyArray: never[] = [] as never[]; + export const resolvingEmptyArray: never[] = [] as never[]; export const emptyMap: ReadonlyMap = createMap(); export const externalHelpersModuleNameText = "tslib"; diff --git a/tests/baselines/reference/circularConstraintYieldsAppropriateError.js b/tests/baselines/reference/circularConstraintYieldsAppropriateError.js new file mode 100644 index 00000000000..a8583219dba --- /dev/null +++ b/tests/baselines/reference/circularConstraintYieldsAppropriateError.js @@ -0,0 +1,52 @@ +//// [circularConstraintYieldsAppropriateError.ts] +// https://github.com/Microsoft/TypeScript/issues/16861 +class BaseType { + bar: T +} + +class NextType extends BaseType { + baz: string; +} + +class Foo extends NextType { + someProp: { + test: true + } +} + +const foo = new Foo(); +foo.bar.test + +//// [circularConstraintYieldsAppropriateError.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +// https://github.com/Microsoft/TypeScript/issues/16861 +var BaseType = /** @class */ (function () { + function BaseType() { + } + return BaseType; +}()); +var NextType = /** @class */ (function (_super) { + __extends(NextType, _super); + function NextType() { + return _super !== null && _super.apply(this, arguments) || this; + } + return NextType; +}(BaseType)); +var Foo = /** @class */ (function (_super) { + __extends(Foo, _super); + function Foo() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Foo; +}(NextType)); +var foo = new Foo(); +foo.bar.test; diff --git a/tests/baselines/reference/circularConstraintYieldsAppropriateError.symbols b/tests/baselines/reference/circularConstraintYieldsAppropriateError.symbols new file mode 100644 index 00000000000..7f485dd282c --- /dev/null +++ b/tests/baselines/reference/circularConstraintYieldsAppropriateError.symbols @@ -0,0 +1,48 @@ +=== tests/cases/compiler/circularConstraintYieldsAppropriateError.ts === +// https://github.com/Microsoft/TypeScript/issues/16861 +class BaseType { +>BaseType : Symbol(BaseType, Decl(circularConstraintYieldsAppropriateError.ts, 0, 0)) +>T : Symbol(T, Decl(circularConstraintYieldsAppropriateError.ts, 1, 15)) + + bar: T +>bar : Symbol(BaseType.bar, Decl(circularConstraintYieldsAppropriateError.ts, 1, 19)) +>T : Symbol(T, Decl(circularConstraintYieldsAppropriateError.ts, 1, 15)) +} + +class NextType extends BaseType { +>NextType : Symbol(NextType, Decl(circularConstraintYieldsAppropriateError.ts, 3, 1)) +>C : Symbol(C, Decl(circularConstraintYieldsAppropriateError.ts, 5, 15)) +>someProp : Symbol(someProp, Decl(circularConstraintYieldsAppropriateError.ts, 5, 26)) +>T : Symbol(T, Decl(circularConstraintYieldsAppropriateError.ts, 5, 43)) +>C : Symbol(C, Decl(circularConstraintYieldsAppropriateError.ts, 5, 15)) +>BaseType : Symbol(BaseType, Decl(circularConstraintYieldsAppropriateError.ts, 0, 0)) +>T : Symbol(T, Decl(circularConstraintYieldsAppropriateError.ts, 5, 43)) + + baz: string; +>baz : Symbol(NextType.baz, Decl(circularConstraintYieldsAppropriateError.ts, 5, 84)) +} + +class Foo extends NextType { +>Foo : Symbol(Foo, Decl(circularConstraintYieldsAppropriateError.ts, 7, 1)) +>NextType : Symbol(NextType, Decl(circularConstraintYieldsAppropriateError.ts, 3, 1)) +>Foo : Symbol(Foo, Decl(circularConstraintYieldsAppropriateError.ts, 7, 1)) + + someProp: { +>someProp : Symbol(Foo.someProp, Decl(circularConstraintYieldsAppropriateError.ts, 9, 33)) + + test: true +>test : Symbol(test, Decl(circularConstraintYieldsAppropriateError.ts, 10, 15)) + } +} + +const foo = new Foo(); +>foo : Symbol(foo, Decl(circularConstraintYieldsAppropriateError.ts, 15, 5)) +>Foo : Symbol(Foo, Decl(circularConstraintYieldsAppropriateError.ts, 7, 1)) + +foo.bar.test +>foo.bar.test : Symbol(test, Decl(circularConstraintYieldsAppropriateError.ts, 10, 15)) +>foo.bar : Symbol(BaseType.bar, Decl(circularConstraintYieldsAppropriateError.ts, 1, 19)) +>foo : Symbol(foo, Decl(circularConstraintYieldsAppropriateError.ts, 15, 5)) +>bar : Symbol(BaseType.bar, Decl(circularConstraintYieldsAppropriateError.ts, 1, 19)) +>test : Symbol(test, Decl(circularConstraintYieldsAppropriateError.ts, 10, 15)) + diff --git a/tests/baselines/reference/circularConstraintYieldsAppropriateError.types b/tests/baselines/reference/circularConstraintYieldsAppropriateError.types new file mode 100644 index 00000000000..7aa57d21b1a --- /dev/null +++ b/tests/baselines/reference/circularConstraintYieldsAppropriateError.types @@ -0,0 +1,50 @@ +=== tests/cases/compiler/circularConstraintYieldsAppropriateError.ts === +// https://github.com/Microsoft/TypeScript/issues/16861 +class BaseType { +>BaseType : BaseType +>T : T + + bar: T +>bar : T +>T : T +} + +class NextType extends BaseType { +>NextType : NextType +>C : C +>someProp : any +>T : T +>C : C +>BaseType : BaseType +>T : T + + baz: string; +>baz : string +} + +class Foo extends NextType { +>Foo : Foo +>NextType : NextType +>Foo : Foo + + someProp: { +>someProp : { test: true; } + + test: true +>test : true +>true : true + } +} + +const foo = new Foo(); +>foo : Foo +>new Foo() : Foo +>Foo : typeof Foo + +foo.bar.test +>foo.bar.test : true +>foo.bar : { test: true; } +>foo : Foo +>bar : { test: true; } +>test : true + diff --git a/tests/cases/compiler/circularConstraintYieldsAppropriateError.ts b/tests/cases/compiler/circularConstraintYieldsAppropriateError.ts new file mode 100644 index 00000000000..848df9642c5 --- /dev/null +++ b/tests/cases/compiler/circularConstraintYieldsAppropriateError.ts @@ -0,0 +1,17 @@ +// https://github.com/Microsoft/TypeScript/issues/16861 +class BaseType { + bar: T +} + +class NextType extends BaseType { + baz: string; +} + +class Foo extends NextType { + someProp: { + test: true + } +} + +const foo = new Foo(); +foo.bar.test \ No newline at end of file