Reputation: 1724
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.
// 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
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));
Upvotes: 1