Repository navigation
Feature request: Ability to parse a stringified number on type-levelΒ #47141
Description
Activity
Looks like a duplicate of #26382.
I would love to have built-in support for
StringToNumber<T>and arithmetic operations likeAdd<M, N>,Subtract<M, N>. I'd think the primary motivation forStringToNumber<T>would be to represent in the type level the conversion of strings to numbers at the value level (e.g.,<T extends string>(x: T): StringToNumber<T> => +xor at least<T extends string>(x: T) => +x as StringToNumber<T>).It's an... interesting approach to say that the motivating example for
StringToNumber<T>is that it enables us to implementAddandSubtractand such via template literal types. That's really neat, but I suspect that splitting strings and shuffling decimal digits around might not be the fastest nor most robust way for the TypeScript compiler to do arithmetic. It's kind of like a child asking their parents for a smartphone "because it's the final ingredient of my elaborate Rube Goldberg machine". Come on, kid, say "so I can text you in case of emergencies" or make up something about how it'll help you study for school. You're saying the quiet part out loud! πReacted by Bruce Pascoe and Vlad SazonauReacted by Bruce Pascoe and Adam VictorHahaha yeah I was too honest with the motivation part, I just innocently wrote what motivated me instead of writing a more saner motivation that would perhaps motivate the typescript team :P and very apt anology btw xD
But yeah as you mentioned perhaps another motivation is to make the
Number-like function's type more complete. Example as of today this wouldn't compile even when there are no problems with it...declare let x: [string, number] let foo: string = x[Number("0")] // ~~~ // Error: `string | number` is not assignable to `string`
With
toNumber("0")which utilizes this feature the above code would compile.Although I would not call this a duplicate of #26382 because it's not, but not even a spiritual duplicate because this feature would only enable arithmetic of number literals, whereas the linked feature (if implemented fully instead of just literals) would have to handle generics too. Which would basically make typescript a dependently typed language, you'll be able to prove things with it...
declare const lhs = <A extends number, B extends number> (a: A, b: B) => (A + B) * (A + B) declare const rhs = <A extends number, B extends number> (a: A, b: B) => A * A + A * B + B * A + B * B const prove = <A extends number, B extends number> (a: A, b: B) => { let x = lhs(a, b) let y: typeof x = rhs(a, b) // If this compiles, which it would, lhs and rhs are equal }
So having things like
+and*on type-level are HUGEEEE features. And if we're not going to fully implement it and decide that thatA + Bwould simply becomenumberthen meh, still useful with literals, but loses it's charm for me.
Although ofc it's still better than doing arithmetic with tricks and when involving decimals you'd have to make it IEEE 754 compat and what not, so yeah even arithmetic that only works with literals is great.Regarding a
StringToNumberintrinsic type: I don't mind it but I think I'd prefer keeping it as it is the description, feels more simple to me. It does make the feature less visible but then a lot of typescript features and tricks are like that :PReacted by Joe Calzaretta and Vlad SazonauI didn't mean to bikeshed the naming; I was using
StringToNumber<T>as a stand-in for however it happens to implemented. If it's not intrinsic, that's fine, as long as you could definetype StringtoNumber<T extends string> = ...yourself.Reacted by Devansh JethmalaniAh gotcha, even I didn't mean to bikeshed on the name, I thought you were suggesting that instead of resolving
"100" extends StringifyNumber<infer X> ? X : neverto100let's keep as it is ienumberand instead have atype StringToNumber<T> = intrinsicwhich would be used instead of the inference trick.- addedDeclinedThe issue was declined as something which matches the TypeScript visionThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScriptAn idea for TypeScript
on Dec 14, 2021 RyanCavanaugh commented
on Dec 14, 2021 MemberMore actionsYou can write
interface StringToNumber { "0": 0, "1": 1, "2": 2, "3": 3, // etc }
if the numbers involved are too high for this approach to be reasonable, you are doing math that is not appropriate to be done in the type system.
Reacted by Vlad SazonauThe math part was just an example, what if one wants to make this compile...
const toNumber = <N extends number>(s: StringifiedNumber<N>) => Number(s) as N type StringifiedNumber<N extends number> = `${N}` const x: 1000 = toNumber("1000")
A type system that allows writing parsers, that added tail call optimization so that users can operate on larger strings and many more things, doesn't want to allow a simple operation of going from
"1000"to1000doesn't make sense to me sir :) I'd love if you reconsider it.You can always state that "We're adding this feature but it's not meant from doing typelevel maths with large numbers" and then people who still would do it (like in case of the rest of typescript features :P) would be on their own.
RyanCavanaugh commented
on Dec 14, 2021 MemberMore actionsWe're adding this feature but it's not meant from doing typelevel maths with large numbers
For context, this never works.
For context, this never works.
Haha yeah I know hence the "like in case of the rest of typescript features :P"
But what I meant was by stating that, the risk of "misusing" it is transferred to them. I'd do the same if I were to publish a library that does typelevel maths, I'd say "TypeScript team has said not to do this with large numbers" and then the risk further gets transferred to the person who actually would be responsible.
In this way one is not deciding for someone else how much risk they should take, as different people have different amounts of courage to take risks. But at the same time I understand not everyone wants to operate from this model, and that's okay.
Eitherway, I'm happy with typescript :P
We can have this trick to parse string to number from 0 to 999 since TS 4.5 has tail recursion optimization
type ToNumber<T extends string, R extends any[] = []> = T extends `${R['length']}` ? R['length'] : ToNumber<T, [1, ...R]>;
Reacted by legend80s, Kanary Hong and Nikita MadeevAh that's nice especially the fact it can go upto 999
Ah that's nice especially the fact it can go upto 999
Yeah, this PR may help
I think this a duplicate ofShould #42938 probably supersede this? In that case, you could just make it part of the syntax.Yeah it's essentially a duplicate as both issues are asking for the same feature
Suggestion
π Search Terms
parse a number from stringified number on type-level, infer a number literal from a template string literal, template string literal to number literal
β Viability Checklist
My suggestion meets these guidelines:
β Suggestion
Currently there is no way to parse
"100"into100on type-level. One could try something like...But
T0is"100", rightfully so. And inferring100instead would be a breaking change. Let's try this...Here
T1isnumberwhen it could have been inferred as100. The feature request is to makeT1100, this too would be a breaking change but I think it'd break things a lot less. I think it makes sense to infer the narrowest possible type when we can.π Motivating Example
Type-level arithmetic used to be impossible with large number prior to template literal types. But now we can stringify numbers on type-level then split them into digits and then do a carry-save addition. This makes type-level arithmetic very fast and "doable". Example adding

12345&6789...But then there's no way to parse the string back to a number.
Type-level arithmetic makes working with dependent-ish types more complete. It'd be great we can have this feature, thanks!