Is it possible to write a utility type Number<T> which accepts a string literal type that can be converted to a number, if not it returns a never type?
type Five = Number<'5'> // `Five` is of the type number 5
And I just wanted to preemptively answer the question about why I want to do this:
The reason I am asking this question is that I am trying to write an Add utility type which adds the number
type createArray<Len, Ele, Arr extends Ele[] = []> = Arr['length'] extends Len ? Arr : createArray<Len, Ele, [Ele, ...Arr]>
type Add<A extends number, B extends number> = [...createArray<A, 1>, ...createArray<B, 1>]['length']
Right now it works
type Answer = Add<3,10> // Answer is 13
But but it only accepts the number type. I wanted to make it also doesn't accept string type so that this also works type Answer = Add<'3','10'>
TypeScript 4.8 will allow converting string literal types to other types like number, bigint or boolean. See this PR.
By creating a utlity type ParseInt we can "cast" a string literal to a number type.
type ParseInt<T> = T extends `${infer N extends number}` ? N : never
type T0 = ParseInt<"1"> // 1
type T1 = ParseInt<"100"> // 100
type T2 = ParseInt<"-13"> // -13
type T3 = ParseInt<"abc"> // never
Here are examples for the other type conversions:
type ParseBigint<T> = T extends `${infer N extends bigint}` ? N : never
type T4 = ParseBigint<"1"> // 1n
type T5 = ParseBigint<"100"> // 100n
type T6 = ParseBigint<"abc"> // never
type ParseBoolean<T> = T extends `${infer N extends boolean}` ? N : never
type T7 = ParseBoolean<"true"> // true
type T8 = ParseBoolean<"false"> // false
type T9 = ParseBoolean<"abc"> // never
Playground
No, there is no way to turn an arbitrary string literal type to a numeric literal type (I generally call this StringToNumber<T>). There was a recent request at microsoft/TypeScript#47141 asking for this, which was declined. It's not something they care to support. There is a still-open issue at microsoft/TypeScript#26382 asking for support for arbitrary mathematics on literal types, which includes asking for StringToNumber<T>; maybe there's still some hope? But I wouldn't count on it.
If all you care about is non-negative whole numbers less than about 1000 (due to restrictions in recursion even with tail-call elimination) then you can implement it yourself with tuple manipulation, similar to how you're doing Add:
type StringToNumber<T extends string, A extends any[] = []> =
T extends keyof [0, ...A] ? A['length'] : StringToNumber<T, [0, ...A]>
And you can see it work:
type Thirteen = StringToNumber<"13">;
// type Thirteen = 13
This is fragile in the same way as Add... if you pass unexpected things you might get slow compiler performance or errors:
// type Nope = Add<0.4, 10>
// Type instantiation is excessively deep and possibly infinite.(2589)
So you could try to limit inputs to valid numeric strings:
type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "";
type NonZero = Exclude<Digit, "0" | "">
type LessThanAThousand = "0" | `${NonZero}${Digit}${Digit}`
type StringToNumber<T extends LessThanAThousand, A extends any[] = []> =
T extends LessThanAThousand ? T extends keyof [0, ...A] ?
A['length'] : StringToNumber<T, [0, ...A]> : never;
type Oops = StringToNumber<"0.4"> // error
// ----------------------> ~~~~~
// Type '"0.4"' does not satisfy the constraint 'LessThanAThousand'.(2344)
So that works.
Still I don't know that I'd recommend anything like this unless there's a very good use case for it. The Add utility type in and of itself isn't something the TS team thinks it's worth supporting (which might be why ms/TS#47141 was declined).
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With