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

type ${number} cannot be used to index numeric index signature #41893

Closed
ajafff opened this issue Dec 9, 2020 · 10 comments
Closed

type ${number} cannot be used to index numeric index signature #41893

ajafff opened this issue Dec 9, 2020 · 10 comments
Labels
Fixed A PR has been merged for this issue

Comments

@ajafff
Copy link
Contributor

ajafff commented Dec 9, 2020

TypeScript Version: 4.2.0-dev.20201209

Search Terms:

Expected behavior:

type ${number} can be used to index numeric index signature

Actual behavior:

Error: Element implicitly has an 'any' type because index expression is not of type 'number'.

Related Issues:

Code

declare let t: `${number}`;

declare let obj: {
    [key: number]: number;
}

const v = obj[t];

Playground Link: Provided

@weswigham
Copy link
Member

weswigham commented Dec 9, 2020

...but should it? ${number} admits strings like "-1.0e1", which a number index signature does not (since such a string, while parseable as a number, does not fill a numeric slot on the object!).

@ajafff
Copy link
Contributor Author

ajafff commented Dec 9, 2020

I recall that I read something about the requirement for ${number} in template literal types to round-trip (parsing as number and stringifying gives the same string).
But testing with const a: `${number}` = '1.0'; shows that this is not the case.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Dec 10, 2020
@RyanCavanaugh
Copy link
Member

The round-tripping is not a requirement, so @weswigham 's analysis is correct

@ajafff ajafff closed this as completed Dec 11, 2020
@jcalz
Copy link
Contributor

jcalz commented Jul 24, 2021

@RyanCavanaugh said

The round-tripping is not a requirement

Hmm, seems like it should be, right? I'd expect `${number}` to correspond exactly to those values `${n}` where n is of type number. Of course, we already look the other way with things like NaN and Infinity, so maybe not. In any case, has the round-trip functionality actually been discussed and declined? Or did it merely not get implemented yet? Is there an existing issue to track it? If not, should I open one?

@RyanCavanaugh
Copy link
Member

Hmm, seems like it should be, right? I'd expect ${number} to correspond exactly to those values ${n} where n is of type number

This would imply that "1.0" wouldn't be a valid ${number}, which I think would be extraordinarily surprising.

@jcalz
Copy link
Contributor

jcalz commented Jul 26, 2021

Wow, my intuition is exactly the opposite; could you articulate why it would be surprising? You must be looking at the class of valid string-to-number inputs, while I'm looking at the class of valid number-to-string outputs, and both of us are thinking "yep, that's the natural meaning of `${number}`". What's the intuition that drives your view?

Mine is that template literal types should correspond to (untagged) template literal values. Such template literals can consume numbers and produce strings, but they don't consume strings to produce numbers. If "1.0" and "0xfacade" are valid `${number}` values, then the type `${number}` doesn't have much, if anything, to do with template literals at all. It's more like "the strings s where Number(s) or +s is not NaN". It gets worse for a type like `foo${number}bar`: you need to say something like "those strings `foo${s}bar` where s is a string where +s is not NaN", instead of the much more straightforward "those strings `foo${n}bar` where n is a number".

Furthermore, the set of valid number-to-string outputs has a use case we already care about in TypeScript: "numeric" object indices. And while the set of string-to-number inputs might be useful, we don't currently have a string-to-number type function (as in #26382) to use with it. So I'd be interested in seeing use cases where the prohibition of "1.0" and "0xfacade" would break things.

@RyanCavanaugh
Copy link
Member

I ran a poll and most people seem to have the reverse interpretation https://twitter.com/SeaRyanC/status/1420059847156928512

@jcalz
Copy link
Contributor

jcalz commented Jul 29, 2021

Oh, well, 78.6% of respondents are wrong! Kidding, kind of. I guess `${number}s` don't lie! But I wonder if you'd have gotten different results if you had asked about

let s4: `${number}%` = "0xfacade%"; // okay or error
(arr: string[], idx: `${number}`) => arr[idx]; // okay or error
let s5: `This sentence is ${boolean}` = "This sentence is 0"; // okay or error
let s6: `${100.0}%` = "100.0%"; // okay or error
let s7: `${number}%` = "              333              %"; // okay or error

instead. In any case, this really makes me wish we had a different notation for strings-that-are-coercible-to-finite-numbers (maybe a new intrinsic type like type numericLiteralString = intrinsic) because the current notation is hard to square with how it's being used.

@ruojianll
Copy link

I think where ${number} appeared in template literal types should be correspond to js or ts number literal in ECMAScript https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-literals-numeric-literals

@ahejlsberg
Copy link
Member

The original issue reported here is fixed by #48837: Numeric index signatures are now applicable for indices of type `${number}`.

With respect to whether `${number}` should require that string literals round-trip, I think there are good arguments on both sides (as demonstrated above). My perspective is that round-trip checking adds little in terms of type safety. Most uses of the `${number}` type in conjunction with indexing really revolve around extracting and constraining index types, and less around checking user written string literals. One might even argue that round-trip checking wouldn't be strict enough because it still permits keys that lead to inefficient (and surprising) array representations at runtime. For example, "42.5" round-trips, but otherwise is treated as a non-numeric property that doesn't count towards the length of an array. If we really wanted strictness we'd need the ability to express non-negative round-tripping numbers. That, in combination with Ryan's point about how extraordinarily surprising it would be if "1.0" wasn't a `${number}`, makes me think we have the right compromise with the current semantics.

@ahejlsberg ahejlsberg added Fixed A PR has been merged for this issue and removed Working as Intended The behavior described is the intended behavior; this is not a bug labels Apr 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests

6 participants