Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inference isn't working with constructor that contextually types 'this' with intersections of itself #12846

Open
DanielRosenwasser opened this issue Dec 12, 2016 · 2 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Dec 12, 2016

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.

@DanielRosenwasser
Copy link
Member Author

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 {}.

@DanielRosenwasser
Copy link
Member Author

Just so that this is easier to search for, this issue is one of the bigger ones for libraries like Vue.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature and removed Effort: Difficult Good luck. labels Mar 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants