Inference isn't working with constructor that contextually types 'this' with intersections of itself
DanielRosenwasser opened this issue · comments
TL;DR: There should be no errors in the below example.
I am trying to model a library that takes an options bag for its constructor, and exposes those properties onto the instance that is eventually produced.
For example, if I wrote
let instance = new Component({
methods: {
hello() { }
world() {
this.hello();
}
}
});
I should be able to write instance.hello()
or instance.world()
.
Additionally, since this
in world
will be bound to the instance, this.hello
should be legal and well-typed in the above example.
However, I'm not able to come up with a version of this that works.
/**
* Ensures all properties have a `this` param of type `T`.
*/
interface ThisEnforced<T> {
[prop: string]: (this: T, ...args: any[]) => any;
}
interface ComponentOptions<Methods> {
methods?: Methods
}
interface Component<Methods extends ThisEnforced<Component<Methods> & Methods>> {
$methods: Methods;
}
interface ComponentStatic {
new <Methods extends ThisEnforced<Component<Methods> & Methods>>(options: ComponentOptions<Methods>):
// Returns...
Component<Methods> & Methods;
}
declare var Component: ComponentStatic;
let inst = new Component({
methods: {
hello() {
// ...
},
world() {
this.hello;
// ~~~~~
// Error: Property `hello` does not exist on this type.
}
},
});
inst.hello;
// ~~~~~
// Error: Property `hello` does not exist on this type.
In the above example, there should be no errors - however, it appears that no inferences are being drawn as a type argument for Methods
, and both this.hello
and inst.hello
are causing errors.
For those wondering, the reason is that the circular dependency between the type of this
and the type of the object being passed in can't be supported using the way our inference process currently works.
Having discussed with @ahejlsberg, the problem is that as soon as we try to check the body of each method, the type of this
is demanded. This requires type arguments that this
depends on in each method (such as the Methods
type argument) to be "fixed".
But since TypeScript needed to figure out the type of the methods
object before it could infer the type of Methods
, it never drew any inferences and just resorted to {}
.
Just so that this is easier to search for, this issue is one of the bigger ones for libraries like Vue.