Issue
I'd like that a static method was forced to return an instance of object that type is the type of its class.
For example:
abstract class AbstractInstance {
static getOwnInstance(): this {
return new (this as any);
}
}
class C1 {
}
class C2 extends AbstractInstance {
static getOwnInstance(): C1 { //=> this method should return an instance of "C2" type
return new C1;
}
}
Solution
The feature you're looking for is a polymorphic this
type that works for static
methods. Unfortunately that feature is not part of TypeScript. There's a longstanding open request at microsoft/TypeScript#5863 for this, but for now we need to work around it.
The standard workaround first seems to be mentioned in this comment on ms/TS#5863, where we use a generic type parameter along with a this
parameter, in order to emulate polymorphic this
:
abstract class AbstractInstance {
a = 1 // added some structure to avoid empty class issues
static getOwnInstance<T extends AbstractInstance>(this: new () => T) {
return new this;
}
}
Here, you can only call getOwnInstance()
on an object whose type is a zero-arg constructor returning some type T
constrained to AbstractInstance
. And then since it returns new this
, the return type of the method is T
.
So, first of all, that means you can't call it on the abstract constructor itself, because it isn't directly new
able:
AbstractInstance.getOwnInstance(); // error!
//~~~~~~~~~~~~~~
// Cannot assign an abstract constructor type to a non-abstract constructor type.
And subclasses will automatically get the appropriate return type:
class C1 extends AbstractInstance {
b = 2
}
const c1 = C1.getOwnInstance();
// ^? const c1: C1
c1.b.toFixed(); // okay
If you try to override getOwnInstance()
, you essentially are forced to do it the same way as the original implementation, in the sense that call signature and return type can't really be any more specific:
class C2 extends AbstractInstance { // error!
//~~
/* Class static side 'typeof C2' incorrectly extends
base class static side 'typeof AbstractInstance'.
The types returned by 'getOwnInstance()' are incompatible between these types.
Type 'C2' is not assignable to type 'T'.
'C2' is assignable to the constraint of type 'T', but 'T' could be
instantiated with a different subtype of constraint 'AbstractInstance
*/
c = 3
static getOwnInstance() {
return new C2(); // <-- oops!
}
}
That failed because C2
isn't known to be appropriate. Indeed if you subclass C2
, then you really don't want C2
:
class C3 extends C2 {
d = 4;
}
C3.getOwnInstance() // should be C3, not C2
So you're constrained to implementing it more or less without alteration:
class C4 extends AbstractInstance {
e = 5
static getOwnInstance<T extends AbstractInstance>(this: new () => T) {
console.log("okay I guess");
return new this;
}
}
So that looks good.
This workaround isn't perfect. It would be nice if you could plausibly constrain, say, T
in C4.getOwnInstance()
to C4
instead of AbstractInstance
, which is what you'd get for free with a true polymorphic this
type. And adding extra generics where you weren't trying to use them is possibly confusing. So hopefully one day ms/TS#5863 will be addressed and there will be a more appealing approach. For now, though, this is about the best we can do.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.