Skip to content

Remove empty intersection types in union types - #18438

Merged
Anders Hejlsberg (ahejlsberg) merged 6 commits into
masterfrom
unionIntersectionUnit
Sep 14, 2017
Merged

Remove empty intersection types in union types#18438
Anders Hejlsberg (ahejlsberg) merged 6 commits into
masterfrom
unionIntersectionUnit

Conversation

@ahejlsberg

@ahejlsbergAnders Hejlsberg (ahejlsberg) commented Sep 13, 2017

Copy link
Copy Markdown
Member

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:

typeT1=(0|1|2)&(1|2|3);// 1 | 2typeT2=(0|1|2)&(3|4|5);// nevertypeT3={a: string}|undefined;typeT4={b: number}|undefined;typeT5=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.

Comment threadsrc/compiler/checker.ts Outdated
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
Copy Markdown
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?

@ahejlsbergAnders Hejlsberg (ahejlsberg) changed the title Remove intersections of unit types in unionsRemove empty intersection types in unit typesSep 13, 2017
@ahejlsberg
Anders Hejlsberg (ahejlsberg) deleted the unionIntersectionUnit branch September 14, 2017 17:44
@zpdDG4gta8XKpMCd

Copy link
Copy Markdown

what about tag types #4895?

typeCustomerId=number&'customer-id';

@mhegazy

Copy link
Copy Markdown
Contributor

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

@mhegazyMohamed Hegazy (mhegazy) added the Breaking Change Would introduce errors in existing code label Sep 14, 2017
@vsiao

Copy link
Copy Markdown

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Breaking ChangeWould 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

@ahejlsberg@zpdDG4gta8XKpMCd@mhegazy@vsiao@weswigham@msftclas