-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Numeric Range Types #29119
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
Numeric Range Types #29119
Conversation
some more items for the wish list:
|
Although the compiler is written to run on environments where |
Well, we support |
Hm, actually yeah it should be possible to set it up in a way that doesn't require BigInt support, I'll take a look at doing that. |
While type-level arithmetics are rejected, this PR seems like an operation |
The syntax of a range is `(OP N)`, where OP is one of <, <=, >, or >=, and N is a numeric literal. The parentheses are mandatory. The parser refers to these as a HalfRangeType. The rules for them are as follows: Assigning to a range from a number or an enum is not allowed. In the future, enums may be supported directly, while numbers will have to be done through control flow analysis. Assigning to a range from a number literal (including enum literals) is allowed if the literal is within the range. Assigning from BigInt literals is not supported as the compiler only treats them as strings. Assigning from a range to another is only allowed if the source range is contained entirely within the target range. For example, assigning from (>= 0) to (> 0) is not allowed, because 0 would be valid in the source but not the target. Assigning from (> 0) to (> 1) is not allowed, but the reverse is. Assigning from a range to a generic number is always allowed. Assigning to an enum is not. Internally, the compiler represents all ranges as "full" ranges, e.g. [0, 1) in interval notation. In the future this will be used in union and intersection types to "merge" ranges. For example, the union of (>= 0) and (< 1) should be [0, 1), while the intersection should be `never`. These should still work normally, but there isn't any special support for them that would support such narrowing. Right now, these "full" ranges are not actually supported in the syntax, nor is this planned. Since this is a first draft, there's not yet any support for language services, and there's very limited diagnostic information available. In the future, control flow analysis will be done to automatically widen and narrow numbers and ranges as necessary.
Assuming strict null checks are turned on, a range can only be falsy if it contains 0.
Just in case they're produced from something else, they should be output into something that makes sense. For obvious reasons, a range with no min and no max is just the number type, while one with both a min and a max is output as an intersection of two half ranges.
For example, (>= 1) is a subtype of (> 0). This doesn't really matter in most cases though, because unions don't often reduce subtypes, and that is the only time where the subtype relation is actually used.
Forgot to when doing the falsiness thing, oops.
5ded73b
to
b231694
Compare
I'm cleaning up our PR backlog and this is one is fairly stale, so I'm going to close it. @ECrownofFire The next step is to create an issue with a complete proposal for numeric range types. You'll need to consider assignability between range types and number types, which can get fairly tricky for type parameters with constraints that are range types. Expect considerable bikeshed discussion on syntax as well, since language designers' favourite quibble is lexical syntax. |
Based on #15480, specifically #15480 (comment) by @AnyhowStep.
This likely needs some more plumbing and more test cases, especially for intersections and unions involving ranges. Ideally I would like for intersections and unions to merge ranges as applicable if they overlap, but similar to subtype reduction in unions, it may not be necessary or wanted. Though it may be good to at least do elimination similar to literals and primitives (e.g.
1 | number
->number
and1 & number
->1
).This is of course a bit more limited than the suggestions in #15480, mainly in that it lacks an
int
type (I considered that a separate issue). This makes it less directly applicable to some use cases, but still provides some much stronger guarantees.So for a couple quick examples:
var foo: (> 0);
denotes a number that must be positive.(< 0) | (> 0)
denotes any number but zero. And(>= -1) & (<= 1)
is any number between -1 and 1, inclusive.A couple limitations/notes:
Performing any sort of arithmetic with a range type will cause the result to degenerate to a plain
number
. I have an alternate branch that does implement this, but e.g. Design Meeting Notes for 4/15/2016 #8112 and numeric literal types disrespect arithmetics #15645 indicate that type arithmetic in general is undesired, so I dropped those commits from this PR.There's no support for anything but a number literal in the range type. This means that, for example, you can't use generics like
type Gt<T> = (> T);
. This was mostly to simplify parsing and such, but it's probably something that could be added. Ideally this would also support the same sort of expressions that enum member initializers do, though that would likely require some reorganizing ofchecker.ts
to genericizeevaluate()
.As the compiler doesn't support bigints, ranges also don't support them.
And a couple possibilities/wish list items for the future:
Flow analysis, such as narrowing numbers to ranges based on comparison checks. Definitely a "nice to have" thing, and could be added later. Could also do things like say, if you have
var foo: (>= 0)
, then you knowif (foo < 0)
can never be true, and that branch is unreachable.Syntax for other range styles, such as the proposed
a..b
, which in interval notation would be[a, b]
, or(>= a) & (<= b)
.