I would like to define generic type for my mixin method that uses "extending class creating functions". But I am unable to define the good syntax in order to do it.
I took the implementation in this answer https://stackoverflow.com/a/76585028/762461
Here is a short and simple example :
// The type for the so called "extending class creating functions"
type Mixable<A extends any[], R> = (
ctor: new (...args: any[]) => any
) => abstract new (...args: A) => R;
type MixinFunction = {
<A1 extends any[], R1>(ctor1: Mixable<A1, R1>): new (...args: any) => R1;
<A1 extends any[], R1, A2 extends any[], R2>
(ctor1: Mixable<A1, R1>, ctor2: Mixable<A2, R2>): new (...args: any) => R1 & R2;
}
const Mixin: MixinFunction = ((_a: Mixable<any[], any>) => {
// this content is only for the example
return class {} as (new (...args: any[]) => any);
});
const Base = (superclass: Newable) => class _Base extends superclass {
foo():number {
return 1;
}
};
const Base2 = <T>(superclass: Newable) => class _Base2 extends superclass {
bar():T {
return 1 as T;
}
};
// class Derived<T> extends Mixin(Base, Base2<T>) doesn't work because of ts(2562)
class Derived<T> extends Mixin(Base, Base2) {
}
class DerivedString extends Derived<string> {}
(new Derived()).foo(); // infered as "number"
(new DerivedString()).bar(); // I would like to infer the return type here as "string"
Unfortunately TypeScript lacks features necessary for you to write this sort of composed generic mixin. The support for higher order type inference from generic functions only works in very specific circumstances, and using
Base2(a generic function that returns a nongeneric class) as aMixable(a generic type that returns a nongeneric function that returns a nongeneric class) isn't one of those circumstances.To begin to support this sort of thing TypeScript would probably need, at the minimum, a loosening of the restriction of accessing type parameters in base class expressions as requested in microsoft/TypeScript#55972. But in general TypeScript can't really express the sort of higher-kinded types that represent the intended operation of
Mixinwhen applied to generic inputs. There's a feature request at microsoft/TypeScript#1213 for that, although again, it's not clear that an implementation for that would automatically fix your issue.For now, this is just beyond TypeScript's abilities.
Instead of trying to get TypeScript to do what it can't, the best I can imagine doing is to figure out what type you expect
Mixin(Base, Base2<T>)to produce, and then just assert that the result is of that type. It's not great, but it's at least possible:That's complicated, mostly because
BaseandBase2have hidden away their actual classes inside the function body scope, so there's no existing name for their instance types. I use theReturnTypeand theInstanceTypeutility types to tease that out. The generics are handled by makingMixeda generic class constructor (that's the<T>innew <T>(...args: any) => ⋯) and then using an instantiation expression to specifyTas the type argument fortypeof Base2.But it works;
Mixedis seen as a generic class so you're allowed to writeclass Derived<T> extends Mixed<T> {}, and thusDerivedStringhas astringreturn type forbar(), as desired.Playground link to code