From 4d70d705c635c34cacc742ef98f27168b667ca7d Mon Sep 17 00:00:00 2001 From: Mohammad Abukhass <121593267+0xkhass@users.noreply.github.com> Date: Mon, 6 Apr 2026 11:16:40 +0000 Subject: [PATCH] fix(checker): allow unknown[] as valid mixin constructor rest param type --- src/compiler/checker.ts | 5 +- .../mixinWithUnknownRestParam.errors.txt | 30 +++++++++++ .../reference/mixinWithUnknownRestParam.js | 36 +++++++++++++ .../mixinWithUnknownRestParam.symbols | 37 +++++++++++++ .../reference/mixinWithUnknownRestParam.types | 54 +++++++++++++++++++ .../classes/mixinWithUnknownRestParam.ts | 17 ++++++ 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/mixinWithUnknownRestParam.errors.txt create mode 100644 tests/baselines/reference/mixinWithUnknownRestParam.js create mode 100644 tests/baselines/reference/mixinWithUnknownRestParam.symbols create mode 100644 tests/baselines/reference/mixinWithUnknownRestParam.types create mode 100644 tests/cases/conformance/classes/mixinWithUnknownRestParam.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0567712f11da3..fd86bc7ced2d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13161,7 +13161,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const s = signatures[0]; if (!s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s)) { const paramType = getTypeOfParameter(s.parameters[0]); - return isTypeAny(paramType) || getElementTypeOfArrayType(paramType) === anyType; + const elementType = getElementTypeOfArrayType(paramType) + return isTypeAny(paramType) + || elementType === anyType + || elementType === unknownType } } return false; diff --git a/tests/baselines/reference/mixinWithUnknownRestParam.errors.txt b/tests/baselines/reference/mixinWithUnknownRestParam.errors.txt new file mode 100644 index 0000000000000..53d9f8aee3149 --- /dev/null +++ b/tests/baselines/reference/mixinWithUnknownRestParam.errors.txt @@ -0,0 +1,30 @@ +mixinWithUnknownRestParam.ts(14,21): error TS2345: Argument of type 'typeof Base' is not assignable to parameter of type 'ClassConstructor'. + Types of construct signatures are incompatible. + Type 'new (x: number) => Base' is not assignable to type 'new (...args: unknown[]) => {}'. + Types of parameters 'x' and 'args' are incompatible. + Type 'unknown' is not assignable to type 'number'. + + +==== mixinWithUnknownRestParam.ts (1 errors) ==== + // Repro for https://github.com/microsoft/TypeScript/issues/29707 + // unknown[] should be a valid mixin constructor constraint, same as any[] + + type ClassConstructor = new (...args: unknown[]) => {} + + function mixin(Class: C) { + return class extends Class {} + } + + class Base { + constructor(public x: number) {} + } + + const Mixed = mixin(Base) + ~~~~ +!!! error TS2345: Argument of type 'typeof Base' is not assignable to parameter of type 'ClassConstructor'. +!!! error TS2345: Types of construct signatures are incompatible. +!!! error TS2345: Type 'new (x: number) => Base' is not assignable to type 'new (...args: unknown[]) => {}'. +!!! error TS2345: Types of parameters 'x' and 'args' are incompatible. +!!! error TS2345: Type 'unknown' is not assignable to type 'number'. + const instance = new Mixed(42) + \ No newline at end of file diff --git a/tests/baselines/reference/mixinWithUnknownRestParam.js b/tests/baselines/reference/mixinWithUnknownRestParam.js new file mode 100644 index 0000000000000..8f6150eb1d4be --- /dev/null +++ b/tests/baselines/reference/mixinWithUnknownRestParam.js @@ -0,0 +1,36 @@ +//// [tests/cases/conformance/classes/mixinWithUnknownRestParam.ts] //// + +//// [mixinWithUnknownRestParam.ts] +// Repro for https://github.com/microsoft/TypeScript/issues/29707 +// unknown[] should be a valid mixin constructor constraint, same as any[] + +type ClassConstructor = new (...args: unknown[]) => {} + +function mixin(Class: C) { + return class extends Class {} +} + +class Base { + constructor(public x: number) {} +} + +const Mixed = mixin(Base) +const instance = new Mixed(42) + + +//// [mixinWithUnknownRestParam.js] +"use strict"; +// Repro for https://github.com/microsoft/TypeScript/issues/29707 +// unknown[] should be a valid mixin constructor constraint, same as any[] +function mixin(Class) { + return class extends Class { + }; +} +class Base { + x; + constructor(x) { + this.x = x; + } +} +const Mixed = mixin(Base); +const instance = new Mixed(42); diff --git a/tests/baselines/reference/mixinWithUnknownRestParam.symbols b/tests/baselines/reference/mixinWithUnknownRestParam.symbols new file mode 100644 index 0000000000000..2bb5590e13737 --- /dev/null +++ b/tests/baselines/reference/mixinWithUnknownRestParam.symbols @@ -0,0 +1,37 @@ +//// [tests/cases/conformance/classes/mixinWithUnknownRestParam.ts] //// + +=== mixinWithUnknownRestParam.ts === +// Repro for https://github.com/microsoft/TypeScript/issues/29707 +// unknown[] should be a valid mixin constructor constraint, same as any[] + +type ClassConstructor = new (...args: unknown[]) => {} +>ClassConstructor : Symbol(ClassConstructor, Decl(mixinWithUnknownRestParam.ts, 0, 0)) +>args : Symbol(args, Decl(mixinWithUnknownRestParam.ts, 3, 29)) + +function mixin(Class: C) { +>mixin : Symbol(mixin, Decl(mixinWithUnknownRestParam.ts, 3, 54)) +>C : Symbol(C, Decl(mixinWithUnknownRestParam.ts, 5, 15)) +>ClassConstructor : Symbol(ClassConstructor, Decl(mixinWithUnknownRestParam.ts, 0, 0)) +>Class : Symbol(Class, Decl(mixinWithUnknownRestParam.ts, 5, 43)) +>C : Symbol(C, Decl(mixinWithUnknownRestParam.ts, 5, 15)) + + return class extends Class {} +>Class : Symbol(Class, Decl(mixinWithUnknownRestParam.ts, 5, 43)) +} + +class Base { +>Base : Symbol(Base, Decl(mixinWithUnknownRestParam.ts, 7, 1)) + + constructor(public x: number) {} +>x : Symbol(Base.x, Decl(mixinWithUnknownRestParam.ts, 10, 16)) +} + +const Mixed = mixin(Base) +>Mixed : Symbol(Mixed, Decl(mixinWithUnknownRestParam.ts, 13, 5)) +>mixin : Symbol(mixin, Decl(mixinWithUnknownRestParam.ts, 3, 54)) +>Base : Symbol(Base, Decl(mixinWithUnknownRestParam.ts, 7, 1)) + +const instance = new Mixed(42) +>instance : Symbol(instance, Decl(mixinWithUnknownRestParam.ts, 14, 5)) +>Mixed : Symbol(Mixed, Decl(mixinWithUnknownRestParam.ts, 13, 5)) + diff --git a/tests/baselines/reference/mixinWithUnknownRestParam.types b/tests/baselines/reference/mixinWithUnknownRestParam.types new file mode 100644 index 0000000000000..d3f07992b6430 --- /dev/null +++ b/tests/baselines/reference/mixinWithUnknownRestParam.types @@ -0,0 +1,54 @@ +//// [tests/cases/conformance/classes/mixinWithUnknownRestParam.ts] //// + +=== mixinWithUnknownRestParam.ts === +// Repro for https://github.com/microsoft/TypeScript/issues/29707 +// unknown[] should be a valid mixin constructor constraint, same as any[] + +type ClassConstructor = new (...args: unknown[]) => {} +>ClassConstructor : ClassConstructor +> : ^^^^^^^^^^^^^^^^ +>args : unknown[] +> : ^^^^^^^^^ + +function mixin(Class: C) { +>mixin : (Class: C) => { new (...args: unknown[]): (Anonymous class); prototype: mixin.(Anonymous class); } & C +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Class : C +> : ^ + + return class extends Class {} +>class extends Class {} : { new (...args: unknown[]): (Anonymous class); prototype: mixin.(Anonymous class); } & C +> : ^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Class : {} +> : ^^ +} + +class Base { +>Base : Base +> : ^^^^ + + constructor(public x: number) {} +>x : number +> : ^^^^^^ +} + +const Mixed = mixin(Base) +>Mixed : { new (...args: unknown[]): mixin.(Anonymous class); prototype: mixin.(Anonymous class); } & ClassConstructor +> : ^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>mixin(Base) : { new (...args: unknown[]): mixin.(Anonymous class); prototype: mixin.(Anonymous class); } & ClassConstructor +> : ^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>mixin : (Class: C) => { new (...args: unknown[]): (Anonymous class); prototype: mixin.(Anonymous class); } & C +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Base : typeof Base +> : ^^^^^^^^^^^ + +const instance = new Mixed(42) +>instance : mixin.(Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>new Mixed(42) : mixin.(Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Mixed : { new (...args: unknown[]): mixin.(Anonymous class); prototype: mixin.(Anonymous class); } & ClassConstructor +> : ^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>42 : 42 +> : ^^ + diff --git a/tests/cases/conformance/classes/mixinWithUnknownRestParam.ts b/tests/cases/conformance/classes/mixinWithUnknownRestParam.ts new file mode 100644 index 0000000000000..2b5a57fa003da --- /dev/null +++ b/tests/cases/conformance/classes/mixinWithUnknownRestParam.ts @@ -0,0 +1,17 @@ +// @strict: true + +// Repro for https://github.com/microsoft/TypeScript/issues/29707 +// unknown[] should be a valid mixin constructor constraint, same as any[] + +type ClassConstructor = new (...args: unknown[]) => {} + +function mixin(Class: C) { + return class extends Class {} +} + +class Base { + constructor(public x: number) {} +} + +const Mixed = mixin(Base) +const instance = new Mixed(42)