Trevor Karjanis
Trevor Karjanis

Reputation: 1724

Is it possible to constrain a type T to include the subtypes of K but not K?

Is it possible to constrain a generic type T to the set of subtypes of type K not including K? I am trying to define a type for inheritance based mixin functions. Question 32488309 provides an answer for the opposite, and coincidentally this question is asked but unanswered in the comments.

TypeScript Playground

// Define a type for the constructor signature.
interface IConstructor { new(...args: any[]): any; }

type Mixin = <T extends IConstructor, U extends T>(Base: T) => U;

// Mix a set of mixins.
function mix(...mixins: Mixin[]) {
  return mixins.reduce((child: IConstructor, mixer) => mixer(child), Object);
}

// Mix typically accepts Mixins with the same constructor signature.
interface ILocalized extends IConstructor {
  new(i18n: I18n): any;
}

function mixinOne(Base: ILocalized) {
  return class MixinOne extends Base {
    constructor(i18n: I18n) { super(i18n); }
  }
}

This results in the following error from question 56505560 which explains that I have achieved the opposite of my goal. T cannot be the set of subtypes of K, because once it is MixinOne it can't be any other.

const LocalizedBase: IBase = mix(mixinOne, ...);

'typeof MixinOne' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'IConstructor'.

Alternatively, the following settles the error, but includes K despite the use of extends.

export type Mixin = (Base: IConstructor) => IConstructor;

I am not interested in the property iterating alternative solution for mixins, because these classes require dependency injection.

Upvotes: 0

Views: 328

Answers (1)

kaya3
kaya3

Reputation: 51112

Here's my solution:

type RequireSubtype<A, B extends A> = A extends B ? never : B

To use it, write a generic function taking a type parameter B extends A, and an actual parameter of type RequireSubtype<A, B>. For example:

class Foo {
    constructor(public readonly x: number) {}
}

class Bar extends Foo {
    constructor(x: number, public readonly y: number) { super(x); }
}

function requireSubtype<T extends Foo>(arg: RequireSubtype<Foo, T>): void {
    // ...
}

// ok
requireSubtype(new Bar(2, 3));
// type error: Argument of type 'Foo' is not assignable to parameter of type 'never'.
requireSubtype(new Foo(1));

Playground Link

Upvotes: 1

Related Questions