diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0567712f11da3..69fbc29e0c720 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13325,6 +13325,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const originalBaseType = baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined; if ( baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class && + // Guard against extending a value whose type is a class instance type (rather than the class + // constructor itself). In that case `baseConstructorType` is the instance type, which shares the + // class symbol but whose construct signatures do not return the instance type, so we must resolve + // the base type from those construct signatures below instead of assuming the instance type. + baseConstructorType !== originalBaseType && areAllOuterTypeParametersApplied(originalBaseType!) ) { // When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the diff --git a/tests/baselines/reference/classExtendsConstructorReturningConstructor.symbols b/tests/baselines/reference/classExtendsConstructorReturningConstructor.symbols new file mode 100644 index 0000000000000..57af0afa7f49e --- /dev/null +++ b/tests/baselines/reference/classExtendsConstructorReturningConstructor.symbols @@ -0,0 +1,41 @@ +//// [tests/cases/compiler/classExtendsConstructorReturningConstructor.ts] //// + +=== classExtendsConstructorReturningConstructor.ts === +// https://github.com/microsoft/TypeScript/issues/63559 +// Extending a value whose type is a class instance type carrying a construct +// signature should resolve the base type from that construct signature rather +// than repeating the outer instance type. + +export declare const Base: { +>Base : Symbol(Base, Decl(classExtendsConstructorReturningConstructor.ts, 5, 20)) + + new (): { + kind: "A"; +>kind : Symbol(kind, Decl(classExtendsConstructorReturningConstructor.ts, 6, 13)) + + new (): { kind: "B" }; +>kind : Symbol(kind, Decl(classExtendsConstructorReturningConstructor.ts, 8, 17)) + + }; +}; + +const A = new class extends Base {}(); +>A : Symbol(A, Decl(classExtendsConstructorReturningConstructor.ts, 12, 5)) +>Base : Symbol(Base, Decl(classExtendsConstructorReturningConstructor.ts, 5, 20)) + +const B = new class extends A {}(); +>B : Symbol(B, Decl(classExtendsConstructorReturningConstructor.ts, 13, 5)) +>A : Symbol(A, Decl(classExtendsConstructorReturningConstructor.ts, 12, 5)) + +const a: "A" = A.kind; +>a : Symbol(a, Decl(classExtendsConstructorReturningConstructor.ts, 15, 5)) +>A.kind : Symbol(kind, Decl(classExtendsConstructorReturningConstructor.ts, 6, 13)) +>A : Symbol(A, Decl(classExtendsConstructorReturningConstructor.ts, 12, 5)) +>kind : Symbol(kind, Decl(classExtendsConstructorReturningConstructor.ts, 6, 13)) + +const b: "B" = B.kind; +>b : Symbol(b, Decl(classExtendsConstructorReturningConstructor.ts, 16, 5)) +>B.kind : Symbol(kind, Decl(classExtendsConstructorReturningConstructor.ts, 8, 17)) +>B : Symbol(B, Decl(classExtendsConstructorReturningConstructor.ts, 13, 5)) +>kind : Symbol(kind, Decl(classExtendsConstructorReturningConstructor.ts, 8, 17)) + diff --git a/tests/baselines/reference/classExtendsConstructorReturningConstructor.types b/tests/baselines/reference/classExtendsConstructorReturningConstructor.types new file mode 100644 index 0000000000000..528885a00e32f --- /dev/null +++ b/tests/baselines/reference/classExtendsConstructorReturningConstructor.types @@ -0,0 +1,64 @@ +//// [tests/cases/compiler/classExtendsConstructorReturningConstructor.ts] //// + +=== classExtendsConstructorReturningConstructor.ts === +// https://github.com/microsoft/TypeScript/issues/63559 +// Extending a value whose type is a class instance type carrying a construct +// signature should resolve the base type from that construct signature rather +// than repeating the outer instance type. + +export declare const Base: { +>Base : new () => { kind: "A"; new (): { kind: "B"; }; } +> : ^^^^^^^^^^ + + new (): { + kind: "A"; +>kind : "A" +> : ^^^ + + new (): { kind: "B" }; +>kind : "B" +> : ^^^ + + }; +}; + +const A = new class extends Base {}(); +>A : (Anonymous class) +> : ^^^^^^^^^^^^^^^^^ +>new class extends Base {}() : (Anonymous class) +> : ^^^^^^^^^^^^^^^^^ +>class extends Base {} : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>Base : { new (): { kind: "B"; }; kind: "A"; } +> : ^^^^^^^^^^ ^^^^^^^^ ^^^ + +const B = new class extends A {}(); +>B : (Anonymous class) +> : ^^^^^^^^^^^^^^^^^ +>new class extends A {}() : (Anonymous class) +> : ^^^^^^^^^^^^^^^^^ +>class extends A {} : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>A : { kind: "B"; } +> : ^^^^^^^^ ^^^ + +const a: "A" = A.kind; +>a : "A" +> : ^^^ +>A.kind : "A" +> : ^^^ +>A : (Anonymous class) +> : ^^^^^^^^^^^^^^^^^ +>kind : "A" +> : ^^^ + +const b: "B" = B.kind; +>b : "B" +> : ^^^ +>B.kind : "B" +> : ^^^ +>B : (Anonymous class) +> : ^^^^^^^^^^^^^^^^^ +>kind : "B" +> : ^^^ + diff --git a/tests/cases/compiler/classExtendsConstructorReturningConstructor.ts b/tests/cases/compiler/classExtendsConstructorReturningConstructor.ts new file mode 100644 index 0000000000000..2d497836aeb5e --- /dev/null +++ b/tests/cases/compiler/classExtendsConstructorReturningConstructor.ts @@ -0,0 +1,20 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/63559 +// Extending a value whose type is a class instance type carrying a construct +// signature should resolve the base type from that construct signature rather +// than repeating the outer instance type. + +export declare const Base: { + new (): { + kind: "A"; + new (): { kind: "B" }; + }; +}; + +const A = new class extends Base {}(); +const B = new class extends A {}(); + +const a: "A" = A.kind; +const b: "B" = B.kind;