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

Unsimpilifed Interesction Types Prevent Type Inference #12289

Closed
ethanresnick opened this issue Nov 16, 2016 · 4 comments
Closed

Unsimpilifed Interesction Types Prevent Type Inference #12289

ethanresnick opened this issue Nov 16, 2016 · 4 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@ethanresnick
Copy link
Contributor

ethanresnick commented Nov 16, 2016

TypeScript Version: 2.2.0-dev.20161112

Code

type Method = 'get' | 'post';
type Method2 = ('get' & string) | ('post' & string)

let app = {
    get() { },
    post() { }
};

let methodA: Method = 'get';
let methodB: Method2 = 'get';

let handlerA = app[methodA]; // () => void
let handlerB = app[methodB]; // Error: Element implicitly has an any type because `{ get(): void, post(): void}` has no index signature

Expected behavior:
handlerA and handlerB should have the same type inferred (because Method and Method2 should be treated as the same type, by simplifying Method2 down to Method).

Actual behavior:
handlerA is inferred correctly; handlerB errors/is implicitly any.

For context, here's the real code (and my attempt to fix it) that produced this problem: #12253 (comment)

@ahejlsberg
Copy link
Member

What's the reason for the 'get' & string types vs. just 'get'?

@ethanresnick
Copy link
Contributor Author

ethanresnick commented Nov 16, 2016

@ahejlsberg This came up in #12253, where I was trying to update the type declarations for Object.entries and Object.keys to take advantage of the keyof operator. My first go at that had a line like this:

keys<T extends { [key: string]: any }>(o: T): (keyof T)[];

The problem was that keyof T is sometimes string | number, so code like this won't type check:

let y: { [x: string]: any } = {}; 

// ... add some keys to y.

Object.keys(y).map(it => it.charAt(0)) // error, charAt is not on number, and `it` is string | number

Note that the code above is just a simplified version of some code from the compiler, which wouldn't build after I tried the above type declaration.

So, my effort to fix it was to use:

keys<T extends { [key: string]: any }>(o: T): (keyof T & string)[];

That makes the code above type check, because the type of it is now string | (string & number). In other cases, though, the & string produces the ("get" & string) | ("post" & string) style types...which should work, except for this issue.

@ahejlsberg
Copy link
Member

Here's how you'd write Object.keys:

interface ObjectConstructor {
    // ...
    keys<T>(obj: T): (keyof T)[];
}

There's no need for a constraint on T. However, not that this approach for typing the of keys method is only correct when T represents the exact type of an object which often isn't the case. For example:

let o: { x: number, y: number };
o = { x: 1, y: 2, z: 3 };
let keys = Object.keys(o);  // Would have type ('x' | 'y')[] which isn't correct

@ethanresnick
Copy link
Contributor Author

ethanresnick commented Nov 17, 2016

keys(obj: T): (keyof T)[];

The problem with that is it won't work for primitives (e.g. Object.keys("foo") === ["0", "1", "2"]), so I was distinguishing primitives from objects with the T extends { [key: string]: any }. Is there a better way to do that? (Regardless, that's kinda tangential.)

However, not that this approach for typing the of keys method is only correct when T represents the exact type of an object which often isn't the case.

Ahh. I hadn't thought about that, and it does seem like a big problem. Does that mean we should give up on using keyof for Object.keys and Object.entries? Or, is there a way to save the approach? In practice, it seems like it's pretty hard to create an object that has extra properties and yet doesn't have an index signature, right? So maybe this isn't a big deal?

@mhegazy mhegazy added the Working as Intended The behavior described is the intended behavior; this is not a bug label Dec 14, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants