You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Hover display for non-homomorphic mapped types renders numeric-string keys indistinguishably from numeric keysΒ #63425
typeA={[Kin1]: K};// ^? type A = { 1: 1; }typeB={[Kin'1']: K};// ^? type B = { 1: "1"; }
π Actual behavior
A and B have keys of different types (1 vs "1"), but the hover renders both keys identically as 1. In B, the display shows key 1 mapping to value "1" β the key and value originate from the same K, so they must be the same type, yet the display suggests they're not.
π Expected behavior
The printer generally prefers the simpler key representation when it's unambiguous (abc rather than "abc"), but numeric literals are themselves valid keys, so numeric-looking strings need to stay quoted to avoid collapsing into a different type. This safeguard is applied correctly in the direct case:
typeDirect={'1': 'a'};// ^? type Direct = { '1': "a"; }
but is lost on the non-homomorphic mapped-type path. B's expected display is { '1': "1"; }.
Additional information about the issue
A related issue (#47214) discussed this for a simpler repro and was closed with the rationale that mapped-type results go through a normalization path without an original spelling to echo. That rationale fits cases where quoting is purely cosmetic; here the unquoted form renders B in a way that obscures which type its keys actually are, so the quoting isn't a style preference but part of what distinguishes string-keyed from number-keyed types in the display.
Note:{[K in keyof T]: ...} renders correctly because it matches the homomorphic mapped type pattern, which preserves the original key representation. Constraints that don't match this pattern, force the general normalization path where the quoting is lost.
You may already be aware of this, but since you haven't mentioned it: there is no such thing as a number key in JavaScript. All property keys are either strings or symbols at runtime; an object initializer like {[1]: 'a'} coerces the 1 to "1" (this is why Object.keys returns string[], for example). And the type system recognizes this, allowing {'1': 'a'}, {1: 'a'}, {[K in '1']: 'a'}, and {[K in 1]: 'a'}to all be mutually assigned to one another.
However there are still observable differences between these types, e.g. keyof will give you either 1 or "1". I wish this wasn't the case (though I also wish fake number keys weren't a thing to begin with), but since it is I can understand the argument that the type display shouldn't obscure it.
EDIT: I'm only now clicking through to #47214 where this is all explained, but I guess it bears repeating here.
Another way to phrase this issue is that B is displayed as a valid but observably-distinct type from what it actually is. Someone may innocently copy this type out of the type info but then get confused when it behaves differently from the original type (for example).
You make excellent points, and in hindsight I should've included the type-system/runtime mismatch context in this issue itself. I opted to scope the issue narrowly to the hover inconsistency, since that's a non-breaking fix. I agree; the deeper fix would be for TypeScript to stop treating numeric literals as distinct key types at all, matching how JS actually behaves but I'm afraid that ship sailed a long time ago π
π€ Thank you for your issue! I've done some analysis to help get you started. This response is automatically generated; feel free to π or π this comment according to its usefulness.
Can we consider this a feature request instead of a bug? There's no guarantee that you can always copy a displayed type and have it be identical to the original type, but it seems preferable to do so where possible, all else being equal.
Given that TypeScript does indeed distinguish between number literal keys and numeric string literal keys, it doesn't help me much to think of {0: any} and {"0": any} as just two different "spellings" of the same type. I mean, keyof {0: any} and keyof {"0": any} are not merely different spellings of the same type, they are different types.
Personally I'd want to see a displayed key name in an object type correspond to at least one valid way of getting the same key in an object literal value. I'm going to say a spelling is "valid" if that works, and "invalid" if it does not. So I'm saying I want to see some valid spelling used for every key name in an object type. If there's an original valid spelling, then it can be used. Otherwise, some valid spelling should be chosen, presumably the simplest or most idiomatic one.
Examples:
The simplest valid spelling of the type 0 is 0. It's valid because in const v = {0: ""}; type K = keyof typeof v, K is 0. Less idiomatic spellings are 0x0 and [0] and 0.000. But "0" is not a valid spelling because in const v = {"0": ""}; type K = keyof typeof v, K is not 0.
Simplest spelling of type -1 is [-1]. But "-1" is invalid.
Simplest spelling of type "2" is "2". But 2 is invalid.
Simplest spelling of type "-3" is "-3". But [-3] is invalid.
Simplest spelling of type "hello" is hello.
Simplest spelling of type "good-bye" is "good-bye".
That implies I want to see something like
typeU={[Kin0|-1|"2"|"-3"|"hello"|"good-bye"]: ""};/* type U = { 0: ""; [-1]: ""; "2": ""; // not 2 "-3": ""; // not [-3] hello: ""; "good-bye": "";} */typeV={[-1]: ""};/* type V = { "[-1]": ""; // not "-1"} */typeW={[Kinkeyof["",""]asKextends `${number}` ? K : never]: ""}/* type W = { "0": ""; // not 0 "1": ""; // not 1} */
Does that seem like a reasonable request? Would it break anything somehow? Would it make some developer experience worse somewhere?
Reacted by Ryan Cavanaugh, Danilo Furrer, Matt Kantor, Minhoru and Lian Harman
Joe Calzaretta (@jcalz) I remember the last time we poked at this, an enormous cloud of bees emerged, but I don't recall exactly what flavor. Worth poking again though
Activity
mkantor commentedon Apr 23, 2026
You may already be aware of this, but since you haven't mentioned it: there is no such thing as a
numberkey in JavaScript. All property keys are eitherstrings orsymbols at runtime; an object initializer like{[1]: 'a'}coerces the1to"1"(this is whyObject.keysreturnsstring[], for example). And the type system recognizes this, allowing{'1': 'a'},{1: 'a'},{[K in '1']: 'a'}, and{[K in 1]: 'a'}to all be mutually assigned to one another.However there are still observable differences between these types, e.g.
keyofwill give you either1or"1". I wish this wasn't the case (though I also wish fakenumberkeys weren't a thing to begin with), but since it is I can understand the argument that the type display shouldn't obscure it.EDIT: I'm only now clicking through to #47214 where this is all explained, but I guess it bears repeating here.
mkantor commentedon Apr 23, 2026
Another way to phrase this issue is that
Bis displayed as a valid but observably-distinct type from what it actually is. Someone may innocently copy this type out of the type info but then get confused when it behaves differently from the original type (for example).codpro2005 commentedon Apr 23, 2026
You make excellent points, and in hindsight I should've included the type-system/runtime mismatch context in this issue itself. I opted to scope the issue narrowly to the hover inconsistency, since that's a non-breaking fix. I agree; the deeper fix would be for TypeScript to stop treating numeric literals as distinct key types at all, matching how JS actually behaves but I'm afraid that ship sailed a long time ago π
RyanCavanaugh commentedon Apr 23, 2026
π€ Thank you for your issue! I've done some analysis to help get you started. This response is automatically generated; feel free to π or π this comment according to its usefulness.
Similar Issues
Here are the most similar issues I found
If your issue is a duplicate of one of these, feel free to close this issue. Otherwise, no action is needed.
jcalz commentedon Apr 24, 2026
Can we consider this a feature request instead of a bug? There's no guarantee that you can always copy a displayed type and have it be identical to the original type, but it seems preferable to do so where possible, all else being equal.
Given that TypeScript does indeed distinguish between
numberliteral keys and numericstringliteral keys, it doesn't help me much to think of{0: any}and{"0": any}as just two different "spellings" of the same type. I mean,keyof {0: any}andkeyof {"0": any}are not merely different spellings of the same type, they are different types.Personally I'd want to see a displayed key name in an object type correspond to at least one valid way of getting the same key in an object literal value. I'm going to say a spelling is "valid" if that works, and "invalid" if it does not. So I'm saying I want to see some valid spelling used for every key name in an object type. If there's an original valid spelling, then it can be used. Otherwise, some valid spelling should be chosen, presumably the simplest or most idiomatic one.
Examples:
0is0. It's valid because inconst v = {0: ""}; type K = keyof typeof v,Kis0. Less idiomatic spellings are0x0and[0]and0.000. But"0"is not a valid spelling because inconst v = {"0": ""}; type K = keyof typeof v,Kis not0.-1is[-1]. But"-1"is invalid."2"is"2". But2is invalid."-3"is"-3". But[-3]is invalid."hello"ishello."good-bye"is"good-bye".That implies I want to see something like
Does that seem like a reasonable request? Would it break anything somehow? Would it make some developer experience worse somewhere?
RyanCavanaugh commentedon Apr 24, 2026
Joe Calzaretta (@jcalz) I remember the last time we poked at this, an enormous cloud of bees emerged, but I don't recall exactly what flavor. Worth poking again though