-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Empty intersection reduction #31838
Empty intersection reduction #31838
Conversation
@typescript-bot test this |
Heya @ahejlsberg, I've started to run the parallelized Definitely Typed test suite on this PR at 015f4c5. You can monitor the build here. It should now contribute to this PR's status checks. |
Heya @ahejlsberg, I've started to run the extended test suite on this PR at 015f4c5. You can monitor the build here. It should now contribute to this PR's status checks. |
RWC test run is clean. DT test run has one minor difference in |
Does this still support branding via object literals? type Tag<T> = T & { readonly brand: unique symbol };
type TaggedNumber = Tag<number>; // not never |
@jack-williams It does, except you can't brand |
@jack-williams The only thing that changes with this PR is when we reduce away empty intersections. We now do it upon construction but we used to do it only when an intersection was put in a union type. The actual patterns we reduce away (or don't reduce away) remain the same. |
Ah, that makes it clearer. Thank you! |
if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) { | ||
// We have seen two distinct unit types which means we should reduce to an | ||
// empty intersection. Adding TypeFlags.NonPrimitive causes that to happen. | ||
includes |= TypeFlags.NonPrimitive; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Counterexample:
enum A {
Zero = 0,
One = 1
}
type Zeroish<T> = T & 0;
type ShouldNotBeNever = Zeroish<A.Zero> | never;
we handle this incorrectly in master
already - I'm just not a fan of propagating the logical error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So is the reason for that that the code highlighted considers A.Zero
and 0
to be distinct? I'm just trying to ascertain where in that example above the never
creeps in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weswigham I think it is debatable whether we want to consider literals and literal enum members with the same underlying value to be intersectable. But either way, it is an issue that should be covered in a separate PR. Here we're just concerned with changing when we reduce, not how.
@typescript-bot run dt |
Heya @ahejlsberg, I've started to run the parallelized Definitely Typed test suite on this PR at 015f4c5. You can monitor the build here. It should now contribute to this PR's status checks. |
RWC and DT test runs are both clean now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect we'll get some reports about the using-an-enum-to-tag break upon release - we should remember to include this in the breaking change log.
Hi I wanted to check to see if this has already been resolved as I am still facing this issue. |
With this PR we reduce empty intersections to
never
immediately upon construction. For example, instead of preserving the typestring & number
and only reducing it away in union types we now immediately reduce it tonever
.Some examples of types that now immediately reduce to never:
In #18438 we chose to preserve empty intersection types (a) to make it easier to understand how they originated and (b) to partially preserve the ability to "tag" primtive types using enum types (e.g.
string & Tag
whereTag
is an enum). In practice this doesn't seem to outweigh the confusion these types bring about, in particular because there are subtle behavioral differences betweennever
and types likestring & number
even though they really should be the same. The differences include:never
is assignable to anything whereasstring & number
is assignable only tostring
ornumber
.any
is not assignable tonever
but is assignable tostring & number
.T
with no index signatures,T[never]
resolves tonever
whereasT[string & number]
is an error.These differences have no practical value. Particularly confusing is that
string & boolean
reduces tonever
(because it normalizes tostring & true | string & false
, which then reduces tonever
), whereasstring & number
sticks around and behaves differently.Fixes #31663.