TypeScript Version: 2.8.0-dev.20180216
Search Terms:
Code
// Behaviour with string keys
type StringKeys1 = {a: any, b: any, c: any}; // Specific keys only
type StringKeys2 = {[s: string]: any}; // Indexer only
type StringKeys3 = {[s: string]: any, foo: any}; // Indexer + some keys
type S1 = keyof StringKeys1; // S1 = "a" | "b" | "c"
type S2 = keyof StringKeys2; // S2 = string
type S3 = keyof StringKeys3; // S3 = string
// Behaviour with numeric keys
type NumberKeys1 = {1: any, 2: any, 3: any}; // Specific keys only
type NumberKeys2 = {[n: number]: any}; // Indexer only
type NumberKeys3 = {[n: number]: any, 42: any}; // Indexer + some keys
type N1 = keyof NumberKeys1; // N1 = "1" | "2" | "3"
type N2 = keyof NumberKeys2; // N2 = never <===== should be string
type N3 = keyof NumberKeys3; // N3 = "42" <===== should be string
Expected behavior:
N2 and N3 should be string.
Actual behavior:
N2 is never and N3 is "42".
Related Issues:
keyof T to allow numbers.lodash typings incorrectly produce never for objects that have numeric indexers.Discussion:
In JavaScript, both string and numeric keys end up effectively being string keys. keyof uniformly handles string keys, numeric keys, and string indexers. Why does it ignore numeric indexers?
cc/ @sandersn
I'm inclined to think you're right that keyof T for some T that includes a numeric index signature should be string. Of course, ideally it would be some string-like type that is constrained to strings containing representations of numbers, but we don't have such a thing. Certainly string seems more accurate than never.
On second thought, it wouldn't work to change this behavior since array and string types have numeric index signatures and all of a sudden would see their keyof T become string instead of the union of member names we now produce. As I alluded to above, the real issue here is that we don't have a type that represents numeric-valued strings, so we can't exactly represent the presence of a numeric index signature.
In practical terms, is there any use case for applying keyof to strings or arrays that would break if the result was just string? I'm trying to think of a useful example.
It is useful that string and arrays have numeric indexers so expressions like str[1] and arr[n] don't raise type errors. But is there a practical use, like a mapped type, for keyof string that makes use of those string members "length" | "toString" | "concat" | ... | "sup"? Or a similar case for arrays?
BTW why does keyof string list the keys of the String interface (concat, toString, valueOf, etc), but keyof object does not list the keys of the Object interface (hasOwnProperty, toString, valueOf, etc)? I assumed it was because for objects they would be more of a pain that useful. Why not the same for strings/arrays?
BTW why does keyof string list the keys of the String interface
Cause implementation wise we treat Object properties differently from the other interfaces.
A related issue from @rhys-vdw:
const numbers: Readonly<{ [id: number]: number }> = { 0: 0 };
const values = Object.values(numbers); // Expected number[], got {}[]
type NumericDictionary<T> = {
[index: number]: T
}
type Mapped<T, S> = {
[P in keyof T]: S
}
// expected { [index: number]: string }, but {} got
type m = Mapped<NumericDictionary<number>, string>;
Is this related, or I should file it as separate issue?
With #23592, keyof { [n: number]: any } now resolves to number.
Most helpful comment
I'm inclined to think you're right that
keyof Tfor someTthat includes a numeric index signature should bestring. Of course, ideally it would be some string-like type that is constrained to strings containing representations of numbers, but we don't have such a thing. Certainlystringseems more accurate thannever.