microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

Home Page:https://www.typescriptlang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.