TypeScript: keyof any incorrectly maps (only) to type string
TypeScript Version: 2.7.1 and 2.8.0-dev.20180215
Search Terms: “keyof any”
Code
function foo<T>(bar: T, baz: keyof T) {
console.log(bar[baz]);
}
const sym = Symbol();
const quirk = { [sym]: "thing" };
foo<any>(quirk, sym);
Expected behavior:
keyof any should be equivalent to PropertyKey (that is string | number | symbol)
The example code should compile without error.
Actual behavior:
keyof any is equivalent to string
The example code does not compile and produces error:
error TS2345: Argument of type 'unique symbol' is not assignable to parameter of type 'string'.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Comments: 25 (3 by maintainers)
I was about to agree but thought it through a bit more. 😛
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
keyofto maintain consistency withObject.keys()because it’s only used to ensure type safety. There is an argument that the ES spec is flawed and 7.3.21 point 4.a. should be changed to include symbols but sure that might break existing code.In effect the type checking provided by
keyofis plain and simply wrong if it doesn’t include symbol keys. The only times it will be used is to ensure nobody tries to improperly index an object. Right now it’s preventing people from properly indexing an object.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): https://tc39.github.io/ecma262/#sec-topropertykey https://tc39.github.io/ecma262/#sec-ispropertykey
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.
What’s your reasoning behind:
keyofmore thansymbolof; andkeyofonly refer to string keys can be usefulIf that is the case then it seems to me to be more explicit/clear to have
keyof,stringofandsymbolof?Thanks 😃
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!
@ogwh that could be fixed either way: either by (a) adding symbols to
keyof, or by (b) addingsymbolofand redefiningReadonly<T>to usekeyof T | symbolof T.So yes, that is a problem with the current definitions, but its not necessary an argument for one approach over the other.
Having
keyofonly refer to string keys can be useful, introducingsymbolofseems to be the right way to go. My guess is most of the time people are going to needkeyofmore thansymbolofanyway. If someone consistently needs both they can just create a type aliastype SymbolKeyOf<T> = keyof T | symbolof T.@ogwh that was in @weswigham’s comment for why
keyofshould be just strings.As for
keyof any, since this is not referring to the keys of any particular object, why not just use thePropertyKeytype in your annotation instead?(There are no
numberproperty keys. All non-symbol property keys are either strings or numeric strings.)