-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
keyof any incorrectly maps (only) to type string #21983
Comments
If function foo<T>(bar: T, baz: keyof T | symbolof T) {
console.log(bar[baz]);
}
const sym = Symbol();
const quirk = { [sym]: "thing" };
foo(quirk, sym); |
(There are no |
Whilst there are technically no number keys in reality they are often used and it would be considered inappropriate to call toString every time you wanted to index an array. In addition we already have PropertyKey defined as the three types. It makes sense to introduce some consistency and use PropertyKey everywhere a key could reasonably be used. That would include in interface definitions and in keyof any. Right now keyof any is string, interface index keys are string|number, PropertyKey is string|number|symbol and in actual usage of an object we use PropertyKey. This isn’t a duplicate because I’m proposing that the inconsistency is the bug and symbolsof would do nothing useful but increase the chaos that we’re already faced with! |
I just think it's not appropriate as a widened type for the result of |
How come? |
I've just come up with a better example that demonstrates the inconsistency more clearly: const foo: any = {};
// Fine
const symKey: symbol = Symbol();
foo[symKey] = null;
// Error
const keyOfKey: keyof any = symKey;
// Also error
const otherKeyOfKey: keyof typeof foo = symKey;
// Compiler output
index.ts(8,7): error TS2322: Type 'symbol' is not assignable to type 'string'.
index.ts(11,7): error TS2322: Type 'symbol' is not assignable to type 'string' |
Another options would be to make |
@ogwh that was in @weswigham's comment for why As for |
I was about to agree but thought it through a bit more. :-P To answer your question @yortus I can't use PropertyKey because in the vast majority of cases the type T is known and so to use anything but keyof T would introduce the possibility of typos. There is no reason for In effect the type checking provided by Also the ECMAScript spec (current draft) is inconsistent itself on this: 6.1.7.3 Invariants of the Essential Internal Methods In this section we have a declaration that keys are either strings or symbols.
Other sections that assert this include (I didn't do an exhaustive check): So in effect we have a standard that accepts property keys are strings and symbols, a backwards compatibility (presumably) to ensure legacy JS code doesn't break at runtime and a type checker that is observing the legacy runtime characteristics rather than the actual type characteristics of properties. The type checker itself also conceding (by the definition of PropertyKey) that a generic property of any old object should conform to compilation-time type checking expectations and not runtime expectations of legacy code. Legacy JS doesn't benefit from Typescript enforcing what is already enforced by the runtime, yet Typescript code suffers because of the inconsistency outlined in the examples already given. With the element type of the array returned by Object.keys always being a subset of the types of PropertyKey there's no danger there. But by keyof T being a subset of the possible actual property key types we end up with broken type checking that excludes possible values. Finally symbolof has no practical purpose once keyof is fixed. If it were to be introduced then it would be equally valid to introduce stringof to complement it if it's accepted that keyof is currently broken. Phew Edit: I apparently wrote keysof in some places instead of keyof. |
Ruminating on this I don't think That being a consequence of objects being used traditionally as maps. So we have two different usages of the word "key". If we look at section 19.1.2.8 we see the proper definition of key used in reference to the getOwnPropertyDescriptors implementation: 19.1.2.8 Object.getOwnPropertyDescriptors ( O )
Edit: OwnPropertyKeys being a reference to section 6.1.7.3 from my previous comment. |
I don't mind having |
Having |
What's your reasoning behind:
If that is the case then it seems to me to be more explicit/clear to have Thanks :-) |
Can you provide a use case where you would need to use |
Yeah I could use an alias. But why use an alias when The hard question is: how should symbols be identified in the string representation of the type generated by Edit: I realise I've called it both "kind of" and "fundamentally" broken. I'm trying not to come across as a knuckhead which is difficult without body language! It is fundamentally broken (imho). :-P |
I've found another bug that emerges as a side effect to this. Because symbols aren't included in class MyIterable {
[Symbol.iterator]() {
const it = (function*() {
for (let s of ["foo", "bar"]) yield s;
})();
return it;
}
}
const collection = new MyIterable();
const readonlyCollection: Readonly<MyIterable> = MyIterable;
for (let value of collection) {
console.log(value);
}
for (let value of readonlyCollection) {
console.log(value);
} tsc output:
|
@ogwh that could be fixed either way: either by (a) adding symbols to So yes, that is a problem with the current definitions, but its not necessary an argument for one approach over the other. |
Perhaps not, but I think I've already made a strong case! That addition is just supplementary! ;-) My biggest fear is that it becomes a case of tl;dr! >_< |
For the busy people I'll summarise:
I think that covers everything. |
@ogwh Here's a simple example of how expanding
Currently works perfectly, but if symbols were included you'd get the resulting type error
When in reality that would not be the correct type to return. In response to some of your summary:
One last note about expanding
it would result in this mess:
if we left
|
The example you gave currently results in a compilation error because It seems that tsc still emits output when there's an error in the file (version 2.7.2), and it also omits the error message when there are errors in other files. It's very bizarre behaviour and seems like a separate bug. But I'm guessing that tricked you! Try the example you gave in a clean environment and you'll get the same error I've included at the end of this comment. 12 is true but it's still a hack around the broken Moreover anywhere a const foo = Symbol();
const bar = {
"foo": "baz",
[foo]: "quirk"
};
function doThing<T, K extends keyof T>(obj: T, key: K) {
// Do the thing
}
doThing(bar, foo); // Bug here
// The coder meant "foo" but left out the quotes, currently this results in a type mismatch.
// However with the change the foo symbol is perfectly valid.
// But, this bug is still possible currently and just as likely as demonstrated by:
const value = bar[foo]; Copied from your example: function values<T, K extends keyof T>(obj: T) {
return Object.keys(obj).map((key: K) => {
return obj[key];
});
} We currently get the error:
I kid you not that's the actual error code too btw! |
There is a breakage when we go in the other direction: function foo(bar: string) {}
const sym = Symbol();
class Thing { [sym] = 42 };
const key: keyof Thing = sym;
foo(sym); // Error here Damn! It would be correct, and would only affect types that already have symbol properties so it's probably very rare but it is no less a breaking change. That's so disappointing. Can we change it anyway?! :'( |
@ogwh it is producing an error because you've enabled strict function types, and is due to the fact that |
Thanks for the clarification, that makes sense. I've filed a new bug in relation to the JS spec which I believe is what has lead to the current situation. There's a reference notification for that just before this comment. I'm fairly sure we can't change this in TS 2, but perhaps once the spec is clear it could be something to change in TS 3? |
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed. |
TypeScript Version: 2.7.1 and 2.8.0-dev.20180215
Search Terms: "keyof any"
Code
Expected behavior:
keyof any
should be equivalent toPropertyKey
(that isstring | number | symbol
)The example code should compile without error.
Actual behavior:
keyof any
is equivalent tostring
The example code does not compile and produces error:
error TS2345: Argument of type 'unique symbol' is not assignable to parameter of type 'string'.
Playground Link:
https://www.typescriptlang.org/play/#src=function%20foo%3CT%3E(bar%3A%20T%2C%20baz%3A%20keyof%20T)%20%7B%0D%0A%20%20%20%20%20%20%20%20console.log(bar%5Bbaz%5D)%3B%0D%0A%7D%0D%0A%0D%0Aconst%20sym%20%3D%20Symbol()%3B%0D%0Aconst%20quirk%20%3D%20%7B%20%5Bsym%5D%3A%20%22thing%22%20%7D%3B%0D%0A%0D%0Afoo%3Cany%3E(quirk%2C%20sym)%3B
The text was updated successfully, but these errors were encountered: