Skip to content
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

Remove empty intersection types in union types #18438

Merged
merged 6 commits into from
Sep 14, 2017
Merged

Conversation

ahejlsberg
Copy link
Member

@ahejlsberg ahejlsberg commented Sep 13, 2017

An intersection of distinct unit types (such as 'a' & 'b' & 'c') is effectively the same as never because it has en empty set of possible values. We allow such types to exist primarily to make it easier to discover their origin (e.g. an intersection of object types containing two properties with the same name). However, when intersecting unions of unit types, the normalization introduced by #11717 ends up making the types very noisy. For example (0 | 1 | 2) & (1 | 2 | 3) becomes 1 | 2 | (0 & 1) | (0 & 2) | (0 & 3) | (1 & 2) | (1 & 3) | (2 & 1) | (2 & 3).

Likewise, when intersecting { a: string } | undefined and { b: number } | undefined (similar to #18210), normalization produces a union of { a: string } & { b: number }, { a: string } & undefined, undefined & { b: number } and undefined. The middle two have empty sets of values, but trip up property accesses through values of the union type.

With this PR we now remove an intersection type from a union type if

  • the intersection type contains more than one unit type, or
  • the intersection type contains an object type and a nullable type (null or undefined).

Some examples:

type T1 = (0 | 1 | 2) & (1 | 2 | 3);  // 1 | 2
type T2 = (0 | 1 | 2) & (3 | 4 | 5);  // never
type T3 = { a: string } | undefined;
type T4 = { b: number } | undefined;
type T5 = T3 & T4;  // ({ a: string} & { b: number }) | undefined

We could in theory be more aggressive about removing empty intersection types, but we don't want to break code that uses intersections to "tag" primitive types.

This implements parts of what is suggested in #16386 and fixes #18210.

@@ -7302,7 +7302,11 @@ namespace ts {
if (flags & TypeFlags.Null) typeSet.containsNull = true;
if (!(flags & TypeFlags.ContainsWideningType)) typeSet.containsNonWideningType = true;
}
else if (!(flags & TypeFlags.Never)) {
else if (!(flags & TypeFlags.Never || flags & TypeFlags.Intersection && every((<IntersectionType>type).types, isUnitType))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than checking if every type is unit, is it not sufficient to check if any two members are unit? number & 3 & 4 is as vacuous as 3 & 4, right?

@ahejlsberg ahejlsberg changed the title Remove intersections of unit types in unions Remove empty intersection types in unit types Sep 13, 2017
@ahejlsberg ahejlsberg merged commit 2077835 into master Sep 14, 2017
@ahejlsberg ahejlsberg deleted the unionIntersectionUnit branch September 14, 2017 17:44
@zpdDG4gta8XKpMCd
Copy link

what about tag types #4895?

type CustomerId = number & 'customer-id';

@mhegazy
Copy link
Contributor

mhegazy commented Sep 14, 2017

Use type CustomerId = number & {'customer-id' : any}; instead

@mhegazy mhegazy added the Breaking Change Would introduce errors in existing code label Sep 14, 2017
@mhegazy mhegazy added this to the TypeScript 2.6 milestone Sep 14, 2017
@vsiao
Copy link

vsiao commented Sep 28, 2017

Is { a: number } & void also considered an empty type?

@sandersn sandersn mentioned this pull request Oct 3, 2017
@ahejlsberg ahejlsberg changed the title Remove empty intersection types in unit types Remove empty intersection types in union types Nov 14, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Breaking Change Would introduce errors in existing code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reduce empty intersections to never
6 participants