Skip to content

Commit 66857b5

Browse files
authored
Merge pull request #11717 from Microsoft/normalizeIntersectionTypes
Normalize union/intersection type combinations
2 parents 0365c96 + bf7f2e2 commit 66857b5

10 files changed

+722
-75
lines changed

src/compiler/checker.ts

+39-31
Original file line numberDiff line numberDiff line change
@@ -5604,6 +5604,11 @@ namespace ts {
56045604
}
56055605
}
56065606

5607+
// We normalize combinations of intersection and union types based on the distributive property of the '&'
5608+
// operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection
5609+
// types with union type constituents into equivalent union types with intersection type constituents and
5610+
// effectively ensure that union types are always at the top level in type representations.
5611+
//
56075612
// We do not perform structural deduplication on intersection types. Intersection types are created only by the &
56085613
// type operator and we can't reduce those because we want to support recursive intersection types. For example,
56095614
// a type alias of the form "type List<T> = T & { next: List<T> }" cannot be reduced during its declaration.
@@ -5613,6 +5618,15 @@ namespace ts {
56135618
if (types.length === 0) {
56145619
return emptyObjectType;
56155620
}
5621+
for (let i = 0; i < types.length; i++) {
5622+
const type = types[i];
5623+
if (type.flags & TypeFlags.Union) {
5624+
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
5625+
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
5626+
return getUnionType(map((<UnionType>type).types, t => getIntersectionType(replaceElement(types, i, t))),
5627+
/*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments);
5628+
}
5629+
}
56165630
const typeSet = [] as TypeSet;
56175631
addTypesToIntersection(typeSet, types);
56185632
if (typeSet.containsAny) {
@@ -6560,52 +6574,46 @@ namespace ts {
65606574

65616575
const saveErrorInfo = errorInfo;
65626576

6563-
// Note that these checks are specifically ordered to produce correct results.
6577+
// Note that these checks are specifically ordered to produce correct results. In particular,
6578+
// we need to deconstruct unions before intersections (because unions are always at the top),
6579+
// and we need to handle "each" relations before "some" relations for the same kind of type.
65646580
if (source.flags & TypeFlags.Union) {
65656581
if (relation === comparableRelation) {
65666582
result = someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
65676583
}
65686584
else {
65696585
result = eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
65706586
}
6571-
65726587
if (result) {
65736588
return result;
65746589
}
65756590
}
6576-
else if (target.flags & TypeFlags.Intersection) {
6577-
result = typeRelatedToEachType(source, target as IntersectionType, reportErrors);
6578-
6579-
if (result) {
6591+
else if (target.flags & TypeFlags.Union) {
6592+
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) {
65806593
return result;
65816594
}
65826595
}
6583-
else {
6584-
// It is necessary to try these "some" checks on both sides because there may be nested "each" checks
6585-
// on either side that need to be prioritized. For example, A | B = (A | B) & (C | D) or
6586-
// A & B = (A & B) | (C & D).
6587-
if (source.flags & TypeFlags.Intersection) {
6588-
// Check to see if any constituents of the intersection are immediately related to the target.
6589-
//
6590-
// Don't report errors though. Checking whether a constituent is related to the source is not actually
6591-
// useful and leads to some confusing error messages. Instead it is better to let the below checks
6592-
// take care of this, or to not elaborate at all. For instance,
6593-
//
6594-
// - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
6595-
//
6596-
// - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
6597-
// than to report that 'D' is not assignable to 'A' or 'B'.
6598-
//
6599-
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
6600-
// breaking the intersection apart.
6601-
if (result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false)) {
6602-
return result;
6603-
}
6596+
else if (target.flags & TypeFlags.Intersection) {
6597+
if (result = typeRelatedToEachType(source, target as IntersectionType, reportErrors)) {
6598+
return result;
66046599
}
6605-
if (target.flags & TypeFlags.Union) {
6606-
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) {
6607-
return result;
6608-
}
6600+
}
6601+
else if (source.flags & TypeFlags.Intersection) {
6602+
// Check to see if any constituents of the intersection are immediately related to the target.
6603+
//
6604+
// Don't report errors though. Checking whether a constituent is related to the source is not actually
6605+
// useful and leads to some confusing error messages. Instead it is better to let the below checks
6606+
// take care of this, or to not elaborate at all. For instance,
6607+
//
6608+
// - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
6609+
//
6610+
// - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
6611+
// than to report that 'D' is not assignable to 'A' or 'B'.
6612+
//
6613+
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
6614+
// breaking the intersection apart.
6615+
if (result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false)) {
6616+
return result;
66096617
}
66106618
}
66116619

src/compiler/core.ts

+6
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,12 @@ namespace ts {
532532
: undefined;
533533
}
534534

535+
export function replaceElement<T>(array: T[], index: number, value: T): T[] {
536+
const result = array.slice(0);
537+
result[index] = value;
538+
return result;
539+
}
540+
535541
/**
536542
* Performs a binary search, finding the index at which 'value' occurs in 'array'.
537543
* If no such index is found, returns the 2's-complement of first index at which

tests/baselines/reference/errorMessagesIntersectionTypes04.errors.txt

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
tests/cases/compiler/errorMessagesIntersectionTypes04.ts(17,5): error TS2322: Type 'A & B' is not assignable to type 'number'.
22
tests/cases/compiler/errorMessagesIntersectionTypes04.ts(18,5): error TS2322: Type 'A & B' is not assignable to type 'boolean'.
33
tests/cases/compiler/errorMessagesIntersectionTypes04.ts(19,5): error TS2322: Type 'A & B' is not assignable to type 'string'.
4-
tests/cases/compiler/errorMessagesIntersectionTypes04.ts(21,5): error TS2322: Type 'number & boolean' is not assignable to type 'string'.
4+
tests/cases/compiler/errorMessagesIntersectionTypes04.ts(21,5): error TS2322: Type '(number & true) | (number & false)' is not assignable to type 'string'.
5+
Type 'number & true' is not assignable to type 'string'.
56

67

78
==== tests/cases/compiler/errorMessagesIntersectionTypes04.ts (4 errors) ====
@@ -33,5 +34,6 @@ tests/cases/compiler/errorMessagesIntersectionTypes04.ts(21,5): error TS2322: Ty
3334

3435
str = num_and_bool;
3536
~~~
36-
!!! error TS2322: Type 'number & boolean' is not assignable to type 'string'.
37+
!!! error TS2322: Type '(number & true) | (number & false)' is not assignable to type 'string'.
38+
!!! error TS2322: Type 'number & true' is not assignable to type 'string'.
3739
}

tests/baselines/reference/intersectionAndUnionTypes.errors.txt

+38-36
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,29 @@ tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(29,1): e
3030
Type 'A & B' is not assignable to type 'C | D'.
3131
Type 'A & B' is not assignable to type 'D'.
3232
Property 'd' is missing in type 'A & B'.
33-
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(31,1): error TS2322: Type 'A & B' is not assignable to type '(A | B) & (C | D)'.
34-
Type 'A & B' is not assignable to type 'C | D'.
33+
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(31,1): error TS2322: Type 'A & B' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
34+
Type 'A & B' is not assignable to type 'B & D'.
3535
Type 'A & B' is not assignable to type 'D'.
36-
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(32,1): error TS2322: Type 'A | B' is not assignable to type '(A | B) & (C | D)'.
37-
Type 'A' is not assignable to type '(A | B) & (C | D)'.
38-
Type 'A' is not assignable to type 'C | D'.
39-
Type 'A' is not assignable to type 'D'.
40-
Property 'd' is missing in type 'A'.
41-
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(33,1): error TS2322: Type 'C & D' is not assignable to type '(A | B) & (C | D)'.
42-
Type 'C & D' is not assignable to type 'A | B'.
36+
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(32,1): error TS2322: Type 'A | B' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
37+
Type 'A' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
38+
Type 'A' is not assignable to type 'B & D'.
39+
Type 'A' is not assignable to type 'B'.
40+
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(33,1): error TS2322: Type 'C & D' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
41+
Type 'C & D' is not assignable to type 'B & D'.
4342
Type 'C & D' is not assignable to type 'B'.
44-
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(34,1): error TS2322: Type 'C | D' is not assignable to type '(A | B) & (C | D)'.
45-
Type 'C' is not assignable to type '(A | B) & (C | D)'.
46-
Type 'C' is not assignable to type 'A | B'.
43+
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(34,1): error TS2322: Type 'C | D' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
44+
Type 'C' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
45+
Type 'C' is not assignable to type 'B & D'.
4746
Type 'C' is not assignable to type 'B'.
4847
Property 'b' is missing in type 'C'.
49-
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(35,1): error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'A & B'.
50-
Type '(A | B) & (C | D)' is not assignable to type 'A'.
51-
Property 'a' is missing in type '(A | B) & (C | D)'.
52-
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(37,1): error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'C & D'.
53-
Type '(A | B) & (C | D)' is not assignable to type 'C'.
54-
Property 'c' is missing in type '(A | B) & (C | D)'.
48+
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(35,1): error TS2322: Type '(A & C) | (A & D) | (B & C) | (B & D)' is not assignable to type 'A & B'.
49+
Type 'A & C' is not assignable to type 'A & B'.
50+
Type 'A & C' is not assignable to type 'B'.
51+
Property 'b' is missing in type 'A & C'.
52+
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(37,1): error TS2322: Type '(A & C) | (A & D) | (B & C) | (B & D)' is not assignable to type 'C & D'.
53+
Type 'A & C' is not assignable to type 'C & D'.
54+
Type 'A & C' is not assignable to type 'D'.
55+
Property 'd' is missing in type 'A & C'.
5556

5657

5758
==== tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts (14 errors) ====
@@ -127,38 +128,39 @@ tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(37,1): e
127128

128129
y = anb;
129130
~
130-
!!! error TS2322: Type 'A & B' is not assignable to type '(A | B) & (C | D)'.
131-
!!! error TS2322: Type 'A & B' is not assignable to type 'C | D'.
131+
!!! error TS2322: Type 'A & B' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
132+
!!! error TS2322: Type 'A & B' is not assignable to type 'B & D'.
132133
!!! error TS2322: Type 'A & B' is not assignable to type 'D'.
133134
y = aob;
134135
~
135-
!!! error TS2322: Type 'A | B' is not assignable to type '(A | B) & (C | D)'.
136-
!!! error TS2322: Type 'A' is not assignable to type '(A | B) & (C | D)'.
137-
!!! error TS2322: Type 'A' is not assignable to type 'C | D'.
138-
!!! error TS2322: Type 'A' is not assignable to type 'D'.
139-
!!! error TS2322: Property 'd' is missing in type 'A'.
136+
!!! error TS2322: Type 'A | B' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
137+
!!! error TS2322: Type 'A' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
138+
!!! error TS2322: Type 'A' is not assignable to type 'B & D'.
139+
!!! error TS2322: Type 'A' is not assignable to type 'B'.
140140
y = cnd;
141141
~
142-
!!! error TS2322: Type 'C & D' is not assignable to type '(A | B) & (C | D)'.
143-
!!! error TS2322: Type 'C & D' is not assignable to type 'A | B'.
142+
!!! error TS2322: Type 'C & D' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
143+
!!! error TS2322: Type 'C & D' is not assignable to type 'B & D'.
144144
!!! error TS2322: Type 'C & D' is not assignable to type 'B'.
145145
y = cod;
146146
~
147-
!!! error TS2322: Type 'C | D' is not assignable to type '(A | B) & (C | D)'.
148-
!!! error TS2322: Type 'C' is not assignable to type '(A | B) & (C | D)'.
149-
!!! error TS2322: Type 'C' is not assignable to type 'A | B'.
147+
!!! error TS2322: Type 'C | D' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
148+
!!! error TS2322: Type 'C' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'.
149+
!!! error TS2322: Type 'C' is not assignable to type 'B & D'.
150150
!!! error TS2322: Type 'C' is not assignable to type 'B'.
151151
!!! error TS2322: Property 'b' is missing in type 'C'.
152152
anb = y;
153153
~~~
154-
!!! error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'A & B'.
155-
!!! error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'A'.
156-
!!! error TS2322: Property 'a' is missing in type '(A | B) & (C | D)'.
154+
!!! error TS2322: Type '(A & C) | (A & D) | (B & C) | (B & D)' is not assignable to type 'A & B'.
155+
!!! error TS2322: Type 'A & C' is not assignable to type 'A & B'.
156+
!!! error TS2322: Type 'A & C' is not assignable to type 'B'.
157+
!!! error TS2322: Property 'b' is missing in type 'A & C'.
157158
aob = y; // Ok
158159
cnd = y;
159160
~~~
160-
!!! error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'C & D'.
161-
!!! error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'C'.
162-
!!! error TS2322: Property 'c' is missing in type '(A | B) & (C | D)'.
161+
!!! error TS2322: Type '(A & C) | (A & D) | (B & C) | (B & D)' is not assignable to type 'C & D'.
162+
!!! error TS2322: Type 'A & C' is not assignable to type 'C & D'.
163+
!!! error TS2322: Type 'A & C' is not assignable to type 'D'.
164+
!!! error TS2322: Property 'd' is missing in type 'A & C'.
163165
cod = y; // Ok
164166

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//// [intersectionTypeNormalization.ts]
2+
interface A { a: string }
3+
interface B { b: string }
4+
interface C { c: string }
5+
interface D { d: string }
6+
7+
// Identical ways of writing the same type
8+
type X1 = (A | B) & (C | D);
9+
type X2 = A & (C | D) | B & (C | D)
10+
type X3 = A & C | A & D | B & C | B & D;
11+
12+
var x: X1;
13+
var x: X2;
14+
var x: X3;
15+
16+
interface X { x: string }
17+
interface Y { y: string }
18+
19+
// Identical ways of writing the same type
20+
type Y1 = (A | X & Y) & (C | D);
21+
type Y2 = A & (C | D) | X & Y & (C | D)
22+
type Y3 = A & C | A & D | X & Y & C | X & Y & D;
23+
24+
var y: Y1;
25+
var y: Y2;
26+
var y: Y3;
27+
28+
interface M { m: string }
29+
interface N { n: string }
30+
31+
// Identical ways of writing the same type
32+
type Z1 = (A | X & (M | N)) & (C | D);
33+
type Z2 = A & (C | D) | X & (M | N) & (C | D)
34+
type Z3 = A & C | A & D | X & (M | N) & C | X & (M | N) & D;
35+
type Z4 = A & C | A & D | X & M & C | X & N & C | X & M & D | X & N & D;
36+
37+
var z: Z1;
38+
var z: Z2;
39+
var z: Z3;
40+
var z: Z4;
41+
42+
// Repro from #9919
43+
44+
type ToString = {
45+
toString(): string;
46+
}
47+
48+
type BoxedValue = { kind: 'int', num: number }
49+
| { kind: 'string', str: string }
50+
51+
type IntersectionFail = BoxedValue & ToString
52+
53+
type IntersectionInline = { kind: 'int', num: number } & ToString
54+
| { kind: 'string', str: string } & ToString
55+
56+
function getValueAsString(value: IntersectionFail): string {
57+
if (value.kind === 'int') {
58+
return '' + value.num;
59+
}
60+
return value.str;
61+
}
62+
63+
//// [intersectionTypeNormalization.js]
64+
var x;
65+
var x;
66+
var x;
67+
var y;
68+
var y;
69+
var y;
70+
var z;
71+
var z;
72+
var z;
73+
var z;
74+
function getValueAsString(value) {
75+
if (value.kind === 'int') {
76+
return '' + value.num;
77+
}
78+
return value.str;
79+
}

0 commit comments

Comments
 (0)