TypeScript Version: 2.3.4, 2.4.0-dev.20170609
Code:
type Constructor<T> = new (...args: any[]) => T;
const Timestamped = <I extends object, CT extends Constructor<I>>(Base: CT) => {
return class extends Base {
timestamp = new Date();
};
};
Expected behavior:
Should be able to use the generic I as an argument to Constructor.
Actual behavior:
Compiling this yields the following error:
error TS2509: Base constructor return type 'I' is not a class or interface type.
Note:
This is a variation on https://github.com/Microsoft/TypeScript/issues/13805 where the following compiles successfully:
type Constructor<T> = new (...args: any[]) => T;
const Timestamped = <CT extends Constructor<object>>(Base: CT) => {
return class extends Base {
timestamp = new Date();
};
};
I've also run into this. This is my workaround which allows a constraint on the base class (IBase) and provides the base type T to the mixin definition.
type Constructor<T> = new (...args: any[]) => T;
interface IBase {
foo: number;
}
const Timestamped = <C extends Constructor<IBase>>(Base: C) =>
<T extends IBase>() =>
class extends Base {
timestamp = new Date;
};
class Base implements IBase {
foo = 5;
}
class Sub extends Timestamped(Base)<Base>() {
bar = 'ok';
}
let s = new Sub;
console.log(s.foo, s.bar, s.timestamp);
I've haven't been able to find a way to avoid the double function call.
I wonder if it possible to return generic class from mixin, like this:
type Constructor<T> = new (...args: any[]) => T;
const List = <T extends Constructor<{}>>(Base: T) => class <T> extends Base {
values: T[];
constructor(...args: any[]) {
super(...args);
}
add(value: T) {
this.values.push(value);
}
};
class Item {
render() { }
}
class ItemList extends List(Item)<Item> {
render() {
this.values.forEach((item) => {
item.render();
});
}
}
const list = new ItemList();
console.log(list.values);
list.render();
For now it's produces error: error TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'., but all vscode hints are ok (list.values are type of Item, its have prototype of Item and so on).
lets fix the problem "is not a class or interface type"
i use TypeScript version 2.9.2/3.0.1 but think it sould work with older ones to
define in your projets global.d.ts
interface ClassType<InstanceType extends {} = {}> extends Function { new(...args: any[]): InstanceType prototype: InstanceType }defined in lib.es5.d.ts
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;
TypeScript infers all types correctly if you change the resolution direction
const Timestamped = <BaseClass extends ClassType<{}>, BaseInstance extends InstanceType<BaseClass>>(base: BaseClass) => class extends base { someTimeAgoIWasA!: BaseInstance constructedFrom!: BaseClass timestamp = new Date(); }To limit the accepted classe/instance type you can change the __{}__ as in this examples:
- BaseClass extends ClassType\<__*Object*__>
- BaseClass extends ClassType\<__*Parent*__>
- BaseClass extends ClassType\<__*...*__>
example only allow to mixin to child/sub classes of the class Parent
class Parent { talkToLuke() { console.log('I am your father!') } } const Timestamped = <BaseClass extends ClassType<Parent>, BaseInstance extends InstanceType<BaseClass>>(base: BaseClass) => class extends base { someTimeAgoIWasA!: BaseInstance constructedFrom!: BaseClass timestamp = new Date(); }
Just as above the InstanceType can be infered as folloing
example Timestamped
const Timestamped = <BaseClass extends ClassType<{}>, BaseInstance extends InstanceType<BaseClass>>(base: BaseClass) => class extends base { someTimeAgoIWasA!: BaseInstance constructedFrom!: BaseClass timestamp = new Date(); } class Base { foo: number = 5 } class Sub extends Timestamped(Base) { bar = 'ok' } const s = new Sub() console.log(s.foo, s.bar, s.timestamp)
Just as above the InstanceType can be infered as folloing
example ItemList
const List = <BaseClass extends ClassType<{}>, BaseInstance extends InstanceType<BaseClass>>(base: BaseClass) => class extends base { values!: BaseInstance[] add(value: BaseInstance) { this.values.push(value) } } class Item { render() { } } class ItemList extends List(Item) { render() { this.values.forEach((item) => item.render()) } } const list = new ItemList() console.log(list.values) list.render()
Most helpful comment
lets fix the problem "is not a class or interface type"
i use TypeScript version 2.9.2/3.0.1 but think it sould work with older ones to
Basic requirement
@athasach
@zamb3zi
@evg656e