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

keyof {[n: number]: any} should be string, not never #22105

Closed
yortus opened this issue Feb 22, 2018 · 9 comments · Fixed by #23592
Closed

keyof {[n: number]: any} should be string, not never #22105

yortus opened this issue Feb 22, 2018 · 9 comments · Fixed by #23592
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@yortus
Copy link
Contributor

yortus commented Feb 22, 2018

TypeScript Version: 2.8.0-dev.20180216

Search Terms:

  • keyof
  • numeric indexer

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:
#13715 seems related but isn't really - it is asking for keyof T to allow numbers.

#22042 has an example showing how 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?

@yortus
Copy link
Contributor Author

yortus commented Feb 22, 2018

cc/ @sandersn

@mhegazy mhegazy added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Feb 22, 2018
@ahejlsberg
Copy link
Member

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.

@ahejlsberg
Copy link
Member

ahejlsberg commented Feb 23, 2018

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.

@yortus
Copy link
Contributor Author

yortus commented Feb 23, 2018

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?

@yortus
Copy link
Contributor Author

yortus commented Feb 23, 2018

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?

@mhegazy
Copy link
Contributor

mhegazy commented Mar 10, 2018

BTW why does keyof string list the keys of the String interface

Cause implementation wise we treat Object properties differently from the other interfaces.

@ghost
Copy link

ghost commented Mar 21, 2018

A related issue from @rhys-vdw:

const numbers: Readonly<{ [id: number]: number }> = { 0: 0 };
const values = Object.values(numbers); // Expected number[], got {}[]

@Strate
Copy link

Strate commented Mar 22, 2018

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?

@ahejlsberg
Copy link
Member

With #23592, keyof { [n: number]: any } now resolves to number.

@ahejlsberg ahejlsberg added the Fixed A PR has been merged for this issue label Apr 20, 2018
@mhegazy mhegazy added this to the TypeScript 2.9 milestone Apr 20, 2018
@mhegazy mhegazy removed the In Discussion Not yet reached consensus label Apr 20, 2018
@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants