Skip to content

Conversation

@ahejlsberg
Copy link
Member

@ahejlsbergahejlsberg 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:

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.

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?

@ahejlsbergahejlsberg changed the title Remove intersections of unit types in unionsRemove empty intersection types in unit typesSep 13, 2017
@ahejlsbergahejlsberg merged commit 2077835 into masterSep 14, 2017
@ahejlsbergahejlsberg deleted the unionIntersectionUnit branch September 14, 2017 17:44
@zpdDG4gta8XKpMCd
Copy link

what about tag types #4895?

typeCustomerId=number&'customer-id';

@mhegazy
Copy link
Contributor

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

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

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

@sandersnsandersn mentioned this pull request Oct 3, 2017
@ahejlsbergahejlsberg changed the title Remove empty intersection types in unit typesRemove empty intersection types in union typesNov 14, 2017
@microsoftmicrosoft locked and limited conversation to collaborators Jun 14, 2018
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

7 participants

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