Prototype: Numeric Literal Types by weswigham · Pull Request #7480 · microsoft/TypeScript (original) (raw)
This is a prototype for numeric literal types, in the same vein as string literal types.
All numeric syntaxes supported by JS should be supported (as far as I know) in type positions. Additionally, NaN
and Infinity
are builtin to the typespace as literal subclasses of number.
Also with this change:
- The unary
-
operator gets contextually typed if its parent is contextually typed and it applies negation to any numeric literal type after it and returns the appropriate type. Ex:-(0x08)
becomes-8
. - Unary
+
and-
is valid in type positions preceeding numeric literal types. Minus makes the following number into a negative number, as you may expect. (Plus is just allowed for symmetry and to clarify +Infinity)
There are tests testing guards against numeric literal types which I'll uncomment/add after #7235 is merged.
My open questions/thoughts:
- I made it so
NaN
andInfinity
have their respective types only when used in type positions. They are still of typenumber
when used as values for compatibility reasons. - Comparability - I'd say that it's a given that we should allow comparisons of types akin to
(1|3) < 2
, but not assign them to one another incorrectly, it's safe to assume that this would need to use the comparable relationship reintroduced in Non-nullable types #7140. (Could also stand to add guards for numeric type unions on <=, <, >, >=) Even beyond that, if we know an enum member has the value3
, should it be assignable to a numeric literal type 2? Probably not. (Though it should likely still be comparable) I've started looking down the rabbit hole of comparing enum members with numeric literal types - it requires the introduction of the concept of another literal type associated with Enum Members to carry out effectively. - Unions of numbers and following the math - Right now, numeric types can flow through arithmetic operators - this is really cool (though it would be cooler if numbers were literal types by default). However, once they're inside a union, this flowing goes away. I could fix this really easily (just apply the arithmetic to all elements in the union, if both sides are unions of numeric types, then for each in each apply the arithmetic), but the downside is this is a really easy way to blow up the number of elements in a union.
- Following the math is dicey in a nullable world -
null
acts as 0 in math, andundefined
causes aNaN
, so technically, all numeric literals after a math operation should be part of a union withNaN
and the-result-if-one-or-both-parameters-is-zero. Which seems ridiculous and makes me want to just say that advanced literal types are only available in strict null mode. - Mutability and type safety - As with strings, mutability widening is probably a good idea for convenience... however, in addition to those measures, mutate-assignment patterns such as
x += 2
wherex
is already of a literal type need to be disallowed (which is only an issue since I made arithmetic operators keep around numeric literal types), OR have their type updated to reflect potential new values (either widened like Mutability widening for literal types #6554 or redefined ala Control flow based type guards #6959). - Flowing numbers through generic parameters into inferred return values - This would make the type system able to act like a symbolic solver at compile time, which is neat. But this would need syntax for stating that a function can take some domain of numeric literal types (rather than just all subtypes of
number
or somesuch) as a parameter type.
I probably had more questions, but I want to put this out for people to see for now. This isn't something that should be considered ready to use, but it should be a great place to start looking at what's feasible.