-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Suggestion: Uniform Type Predicate, or Concrete Types #28430
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
Comments
I’m thinking of having a stab at implementing this, probably over the holidays. Would anyone be interested? |
Initial ramblings here |
I though the purpose of guarding union type may be possible by some hack in current version of typescript (3.2.2). There is some trick exists to cast union to intersection And union never extends its transfromed intersection So, force the type parameter to extend its transformed union will guard the unions away.
|
@mmis1000 There are some workarounds to some of the issues, but these don't really solve the underlying problem. The union to intersection trick does not work for many cases, including booleans. The main problem is that it only superficially helps by preventing you calling the function with arguments with union type. What you really want is for the compiler to be able to exploit this in the body function; to be able to do smarter reasoning and narrowing. I'm not sure the compiler is ever going to be able to exploit a constraint such as |
Related: #22628 |
For anyone interested: I'm going to work on a modified proposal based on some initial work I've tried out. |
Prototype here: #30284 |
I just stubled over this limitation and thought the following approach could be a clean solution with minimal potential for unwanted side-effects: Currently, within the function body, you can use explicit type assertion to make union type guards work under the assumtion that the type of argument b is identical to the type of argument a:
However, it should be possible to use the same notation right in the declaration:
Currently, this doesn't work, but I see no logical reason why it shouldn't |
How do you rule out call-sites like? const a: Pet = { legs: 4, makes: 'meow', lives: 9 };
const b: Pet = { legs: 4, makes: 'wuff' };
killACat(a, b); From the checker POV |
Probably the relevant observation is that "Are these two values of the same type?" is not an meaningfully answerable question in a type system like TypeScript's. The only meaningful questions are more of the form "Are these two values both in this type's domain?". Even "Which type (if any) subsumes both of these values?" is one with many possible correct answers. |
Yep! This suggestion is really just about being able the restrict types such that run time checks can give us meaningful and unique answers to questions such as: “Which (useful) type subsumes both of these values, or all values of this type?” |
Suggestion: Uniform Type Predicate, or Concrete Types
Summary
Narrowing tells us information about a value but not so much about the type of the value, this is because a type may over approximate a value. The over approximation poses a problem when using multiples values of the same type: narrowing information is not sharable because over approximation can allow non-uniform behaviour with respect to operations such as
typeof
or===
.As a canonical example:
Inside the if branch what do we know about
y
, a variable with the same type asx
that we know to be a number. Sadly, nothing. A caller may instantiateT
to beunknown
and thereforey
could be any other value.Proposal
Support a way to define uniform, or concrete, types. A uniform type is one where all values of that type behave uniformly with respect to some operation. The idea is taken from Julia, where concrete types are defined as:
A concrete type T describes the set of values whose direct tag, as returned by the typeof function, is T. An abstract type describes some possibly-larger set of values.
The obvious candidate of operation is
typeof
, but this could be extended to include equality for literal types, or key sets for objects.Basic Example
The introduction syntax is very much up for grabs, but for the examples we can use a constraint.
The constraint
Concrete<"typeof">
ofT
says thatT
may only be instantiated with types where all values of that type behave uniformly with respect totypeof
. Whenx
is a number, then so isy
. The following call-sites demonstrate legal/illegal instantiations.Examples from related issues (1)
#27808
We write
Concrete<number | string, "typeof">
for a type that is either a string or number, but is also concrete. As the values of the array are concrete, a single witness fortypeof
is enough to narrow the type of the entire array.Examples from related issues (2)
#24085
Here we show a use case for defining uniformity of equality.
The issue presented here is that over-approximation leads to unsound uses:
However with a concrete constraint
Concrete<TypeEnum, "===">
we enforce that the parameter is assignable toTypeEnum
, however any instantiation must be uniform with respect to equality, restricting instantiations to singleton types. Example calls:The text was updated successfully, but these errors were encountered: