- Notifications
You must be signed in to change notification settings - Fork 13.2k
Object.keys() types refinement, and Object.entries() types bugfix#12253
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
Uh oh!
There was an error while loading. Please reload this page.
Conversation
ethanresnick commented Nov 15, 2016 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
1. Special case array. Because array instances are typed as implementing an interface that lists all their public methods, `keyof Array` returns all those method names as keys…even though they’re not actually enumerable at runtime. 2. Intersect keyof T with string, because `keyof T` can return `number | string` in some cases, whereas the entries’ keys will always be strings.
Applying the same logic used on Object.entries in the prior commit and in microsoft#12207
msftclas commented Nov 15, 2016
Hi @ethanresnick, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution! The agreement was validated by Microsoft and real humans are currently evaluating your PR. TTYL, MSBOT; |
src/lib/es5.d.ts Outdated
| * @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object. | ||
| */ | ||
| keys<T>(o: Array<T>): string[]; | ||
| keys<T extends{[key: string]: any }>(o: T): (keyof T & string)[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
key of T & string is just string. so this does not help you much.
keys<T>(o: Array<T>): string[];keys<T>(o: T): (keyofT)[];
ethanresnickNov 15, 2016 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mhegazy Edit: sorry, I misread your comment.
key of T & string is often ("key1" & string) | ("key2" & string) | ... | ("keyn" & string), not just string.
The intersection is needed because sometimes keyof T is string | number, and we want to exclude number as a possible type for the entry keys. See this test case: https://github.com/Microsoft/TypeScript/pull/12253/files#diff-3ed8d911aae864ffc1d88e62bcb8dc47R15
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These intersections do make the intellisense popups uglier, but they're required for correctness.
Could the solution to that ugliness be for the compiler to do some work simplifying intersections (and unions)? E.g., 'a' & string could just be simplified to to 'a' (or, more generally, type & subType could just be simplified to subType). Any efforts in that direction could also help with the bug I brought up in #11426. Would this be that hard? I'd kind of expect simplifying logical formulations to be a somewhat solved problem with theorem provers etc, though I haven't looked into it, so maybe that's incredibly naive. Or maybe it's way too slow to apply here in the general case. Still, specific simplification rules might be a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would rather leave it string[] then. adding the & string removes any type safety the literal types give you.
ethanresnickNov 16, 2016 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding the & string removes any type safety the literal types give you.
I don't really understand that. Why would you lose type safety? The & string version still only allows certain strings, rather than all strings, which is more safe.
Let me give the real code example that motivated these two PRs:
constloggers={info: ...,warn: ...,error: ... }Object.entries(loggers).forEach(([name,logger])=>{// In here, I want typeof name to be "info" | "warn" | "error", so that // the line below will fail if I add a key to loggers by mistake that// doesn't have a corresponding method on console. (This in fact// happened: I originally had a debug logger, but only later realized// that there is no console.debug() on the node console object.)logger.log=console[name].bind(console);})To make that code work, string[] isn't enough for typeof name. The change merged in #12207 meanwhile, works perfectly, but fails in the somewhat obscure case that motivated the & string addition in this PR. Using & string, the type of name is inferred as ("info" & string) | ("warn" & string) | ("error" & string). That should, if I understand correctly, be equivalent to "info" | "warn" | "error". However, I instead get an error. That seems like a separate bug, though (opened issue #12289 for it), also tied to simplification.
Going forward... if #12289 were addressed, I think the & string would be the right type signature for Object.keys/entries. But, if #12289 can't be easily addressed, maybe it's better to ignore case that & string was meant to solve, rather than giving up on having key-named literals entirely. If we did that, the signature would be:
keys<T>(o: Array<T>): string[];keys<Textends{[key: string]: any}>(o: T): (keyofT)[];keys(o: any): string[];Then, Object.keys(loggers) would correctly be ("info" | "warn" | "error")[], but let y:{[x: string]: any } ={}; Object.keys(y) would be (string | number)[] rather than string[]. Frankly, that sounds like a net win.
Now that keyof T only returns string or string subtypes (microsoft#12425), there’s no need to do keyof T & string.
ethanresnick commented Nov 27, 2016
@mhegazy I've updated this PR to replace Cc @ahejlsberg: you expressed skepticism about typing Object.keys with keyof in #12289 (comment). But if/now that |
ahejlsberg commented Nov 27, 2016
@ethanresnick My reservations about BTW, I have the same reservations about Note that the changes we made to |
ethanresnick commented Nov 28, 2016
@ahejlsberg What you're saying makes sense, assuming I'm understanding it correctly, makes sense. I'll close this PR then. Feel free to revert #12207 as well. |
Deilan commented Sep 17, 2017
@ahejlsberg And what about using it on |
dyst5422 commented Sep 26, 2017 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
When an object has an index though, shouldn't it return the index type? consta: {[key: number]: any}={};Object.keys(a)// should be a numberBasically with an index, we are saying that the index HAS to be a certain type, so the above concern about dynamic keys doesn't follow. ...unless javascript casts to strings in Object.keys... |
NN--- commented Oct 13, 2017
@dyst5422 Keys are returner as strings. var a={1:2}; |
ccorcos commented Nov 29, 2017
@ahejlsberg I'm not sure I understand your reasoning. If you're object has type |
pelotom commented Nov 29, 2017 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
@ccorcos no, because lots of objects with other extraneous keys can have type declareconstx: {a: 1,b: 2,c: 3}consty: {a: 1;b: 2}=xfor(constkinObject.keys(y)){// what should be the type of k?}
|
ccorcos commented Nov 30, 2017
I see. Sounds like that should be a type error then. haha |
aaronbeall commented Dec 18, 2017 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
I understand the reasoning here and it makes sense, but interfaceFoo{foo: string;}declareconstfoo: FooObject.keys(foo).forEach((key: keyofFoo)=>{})// Error
Which is a pain because with Object.keys(foo).forEach(key=>{foo[key]// Error: Element implicitly has an 'any' type because type 'Foo' has no index signature.})You have to assert That's pretty annoying. Is there no way we could allow |
chriskrycho commented Dec 22, 2017
Yeah, the only other workaround also ends up being kind of gross to write. You can write it, but it's gross: (Object.keys(foo)as(keyofFoo)[]).forEach((key)=>{ ... })I just found my way here after writing up an issue around that b/c of the really poor ergonomics associated with it. Perhaps this could work if the exact types proposal landed? 🤔 |
pelotom commented Dec 22, 2017 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
I'm not sure why this is worth debating here. It's really easy to write your own function which does this "at your own risk" if you want it: exportfunctionkeys<O>(o: O){returnObject.keys(o)as(keyofO)[];} |
chriskrycho commented Dec 22, 2017
FWIW, my comment wasn't offered as "debate." I was mostly thinking out loud if there were ways to get both what many users will expect and satisfy the point made up-thread last year. 🙂 |
domoritz commented Jun 1, 2018
I believe the updated version for 2.9 is exportconstkeys=Object.keysas<T>(o: T)=>(Extract<keyofT,string>)[]; |
Per @mhegazy's suggestion in #12207, this pr applies the logic from that PR to
Object.keys.It also fixes a bug in that PR (namely, supporting
Object.entries([/* some array */])) and adds a test case for that.Finally, it requires that the keys in Object.keys and Object.entries entries be strings, even though the keyof operator can return(That part of the PR is now covered by #12425.)string | number.