Skip to content

Commit ad322a5

Browse files
authored
Merge pull request #31838 from microsoft/emptyIntersectionReduction
Empty intersection reduction
2 parents bc7f3db + 015f4c5 commit ad322a5

File tree

51 files changed

+1078
-229
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1078
-229
lines changed

Diff for: src/compiler/checker.ts

+25-35
Original file line numberDiff line numberDiff line change
@@ -9522,44 +9522,13 @@ namespace ts {
95229522
return false;
95239523
}
95249524

9525-
// Return true if the given intersection type contains
9526-
// more than one unit type or,
9527-
// an object type and a nullable type (null or undefined), or
9528-
// a string-like type and a type known to be non-string-like, or
9529-
// a number-like type and a type known to be non-number-like, or
9530-
// a symbol-like type and a type known to be non-symbol-like, or
9531-
// a void-like type and a type known to be non-void-like, or
9532-
// a non-primitive type and a type known to be primitive.
9533-
function isEmptyIntersectionType(type: IntersectionType) {
9534-
let combined: TypeFlags = 0;
9535-
for (const t of type.types) {
9536-
if (t.flags & TypeFlags.Unit && combined & TypeFlags.Unit) {
9537-
return true;
9538-
}
9539-
combined |= t.flags;
9540-
if (combined & TypeFlags.Nullable && combined & (TypeFlags.Object | TypeFlags.NonPrimitive) ||
9541-
combined & TypeFlags.NonPrimitive && combined & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) ||
9542-
combined & TypeFlags.StringLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) ||
9543-
combined & TypeFlags.NumberLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) ||
9544-
combined & TypeFlags.BigIntLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) ||
9545-
combined & TypeFlags.ESSymbolLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) ||
9546-
combined & TypeFlags.VoidLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) {
9547-
return true;
9548-
}
9549-
}
9550-
return false;
9551-
}
9552-
95539525
function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) {
95549526
const flags = type.flags;
95559527
if (flags & TypeFlags.Union) {
95569528
return addTypesToUnion(typeSet, includes, (<UnionType>type).types);
95579529
}
9558-
// We ignore 'never' types in unions. Likewise, we ignore intersections of unit types as they are
9559-
// another form of 'never' (in that they have an empty value domain). We could in theory turn
9560-
// intersections of unit types into 'never' upon construction, but deferring the reduction makes it
9561-
// easier to reason about their origin.
9562-
if (!(flags & TypeFlags.Never || flags & TypeFlags.Intersection && isEmptyIntersectionType(<IntersectionType>type))) {
9530+
// We ignore 'never' types in unions
9531+
if (!(flags & TypeFlags.Never)) {
95639532
includes |= flags & TypeFlags.IncludesMask;
95649533
if (flags & TypeFlags.StructuredOrInstantiable) includes |= TypeFlags.IncludesStructuredOrInstantiable;
95659534
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
@@ -9783,13 +9752,18 @@ namespace ts {
97839752
}
97849753
}
97859754
else {
9786-
includes |= flags & TypeFlags.IncludesMask;
97879755
if (flags & TypeFlags.AnyOrUnknown) {
97889756
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
97899757
}
97909758
else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
9759+
if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) {
9760+
// We have seen two distinct unit types which means we should reduce to an
9761+
// empty intersection. Adding TypeFlags.NonPrimitive causes that to happen.
9762+
includes |= TypeFlags.NonPrimitive;
9763+
}
97919764
typeSet.push(type);
97929765
}
9766+
includes |= flags & TypeFlags.IncludesMask;
97939767
}
97949768
return includes;
97959769
}
@@ -9905,7 +9879,23 @@ namespace ts {
99059879
function getIntersectionType(types: ReadonlyArray<Type>, aliasSymbol?: Symbol, aliasTypeArguments?: ReadonlyArray<Type>): Type {
99069880
const typeSet: Type[] = [];
99079881
const includes = addTypesToIntersection(typeSet, 0, types);
9908-
if (includes & TypeFlags.Never) {
9882+
// An intersection type is considered empty if it contains
9883+
// the type never, or
9884+
// more than one unit type or,
9885+
// an object type and a nullable type (null or undefined), or
9886+
// a string-like type and a type known to be non-string-like, or
9887+
// a number-like type and a type known to be non-number-like, or
9888+
// a symbol-like type and a type known to be non-symbol-like, or
9889+
// a void-like type and a type known to be non-void-like, or
9890+
// a non-primitive type and a type known to be primitive.
9891+
if (includes & TypeFlags.Never ||
9892+
strictNullChecks && includes & TypeFlags.Nullable && includes & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.IncludesEmptyObject) ||
9893+
includes & TypeFlags.NonPrimitive && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) ||
9894+
includes & TypeFlags.StringLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) ||
9895+
includes & TypeFlags.NumberLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) ||
9896+
includes & TypeFlags.BigIntLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) ||
9897+
includes & TypeFlags.ESSymbolLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) ||
9898+
includes & TypeFlags.VoidLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) {
99099899
return neverType;
99109900
}
99119901
if (includes & TypeFlags.Any) {

Diff for: src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3983,7 +3983,7 @@ namespace ts {
39833983
NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | StructuredOrInstantiable,
39843984
// The following flags are aggregated during union and intersection type construction
39853985
/* @internal */
3986-
IncludesMask = Any | Unknown | Primitive | Never | Object | Union,
3986+
IncludesMask = Any | Unknown | Primitive | Never | Object | Union | NonPrimitive,
39873987
// The following flags are used for different purposes during union and intersection type construction
39883988
/* @internal */
39893989
IncludesStructuredOrInstantiable = TypeParameter,

Diff for: tests/baselines/reference/indexingTypesWithNever.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ declare function genericFn3<
6969

7070
// Should be never
7171
const result5 = genericFn3({ g: "gtest", h: "htest" }, "g", "h"); // 'g' & 'h' will reduce to never
72-
>result5 : unknown
73-
>genericFn3({ g: "gtest", h: "htest" }, "g", "h") : unknown
72+
>result5 : never
73+
>genericFn3({ g: "gtest", h: "htest" }, "g", "h") : never
7474
>genericFn3 : <T extends { [K in keyof T]: T[K]; }, U extends keyof T, V extends keyof T>(obj: T, u: U, v: V) => T[U & V]
7575
>{ g: "gtest", h: "htest" } : { g: string; h: string; }
7676
>g : string

Diff for: tests/baselines/reference/inferTypes1.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ tests/cases/conformance/types/conditional/inferTypes1.ts(145,40): error TS2322:
107107
type T50 = X3<{}>; // never
108108
type T51 = X3<{ a: (x: string) => void }>; // never
109109
type T52 = X3<{ a: (x: string) => void, b: (x: string) => void }>; // string
110-
type T53 = X3<{ a: (x: number) => void, b: (x: string) => void }>; // string & number
110+
type T53 = X3<{ a: (x: number) => void, b: (x: string) => void }>; // never
111111
type T54 = X3<{ a: (x: number) => void, b: () => void }>; // number
112112

113113
type T60 = infer U; // Error

Diff for: tests/baselines/reference/inferTypes1.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ type X3<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U
6969
type T50 = X3<{}>; // never
7070
type T51 = X3<{ a: (x: string) => void }>; // never
7171
type T52 = X3<{ a: (x: string) => void, b: (x: string) => void }>; // string
72-
type T53 = X3<{ a: (x: number) => void, b: (x: string) => void }>; // string & number
72+
type T53 = X3<{ a: (x: number) => void, b: (x: string) => void }>; // never
7373
type T54 = X3<{ a: (x: number) => void, b: () => void }>; // number
7474

7575
type T60 = infer U; // Error

Diff for: tests/baselines/reference/inferTypes1.symbols

+1-1
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ type T52 = X3<{ a: (x: string) => void, b: (x: string) => void }>; // string
297297
>b : Symbol(b, Decl(inferTypes1.ts, 69, 39))
298298
>x : Symbol(x, Decl(inferTypes1.ts, 69, 44))
299299

300-
type T53 = X3<{ a: (x: number) => void, b: (x: string) => void }>; // string & number
300+
type T53 = X3<{ a: (x: number) => void, b: (x: string) => void }>; // never
301301
>T53 : Symbol(T53, Decl(inferTypes1.ts, 69, 66))
302302
>X3 : Symbol(X3, Decl(inferTypes1.ts, 63, 52))
303303
>a : Symbol(a, Decl(inferTypes1.ts, 70, 15))

Diff for: tests/baselines/reference/inferTypes1.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,8 @@ type T52 = X3<{ a: (x: string) => void, b: (x: string) => void }>; // string
211211
>b : (x: string) => void
212212
>x : string
213213

214-
type T53 = X3<{ a: (x: number) => void, b: (x: string) => void }>; // string & number
215-
>T53 : number & string
214+
type T53 = X3<{ a: (x: number) => void, b: (x: string) => void }>; // never
215+
>T53 : never
216216
>a : (x: number) => void
217217
>x : number
218218
>b : (x: string) => void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
tests/cases/conformance/types/intersection/intersectionReduction.ts(40,1): error TS2322: Type 'any' is not assignable to type 'never'.
2+
tests/cases/conformance/types/intersection/intersectionReduction.ts(41,1): error TS2322: Type 'any' is not assignable to type 'never'.
3+
4+
5+
==== tests/cases/conformance/types/intersection/intersectionReduction.ts (2 errors) ====
6+
declare const sym1: unique symbol;
7+
declare const sym2: unique symbol;
8+
9+
type T1 = string & 'a'; // 'a'
10+
type T2 = 'a' & string & 'b'; // never
11+
type T3 = number & 10; // 10
12+
type T4 = 10 & number & 20; // never
13+
type T5 = symbol & typeof sym1; // typeof sym1
14+
type T6 = typeof sym1 & symbol & typeof sym2; // never
15+
type T7 = string & 'a' & number & 10 & symbol & typeof sym1; // never
16+
17+
type T10 = string & ('a' | 'b'); // 'a' | 'b'
18+
type T11 = (string | number) & ('a' | 10); // 'a' | 10
19+
20+
type N1 = 'a' & 'b';
21+
type N2 = { a: string } & null;
22+
type N3 = { a: string } & undefined;
23+
type N4 = string & number;
24+
type N5 = number & object;
25+
type N6 = symbol & string;
26+
type N7 = void & string;
27+
28+
type X = { x: string };
29+
30+
type X1 = X | 'a' & 'b';
31+
type X2 = X | { a: string } & null;
32+
type X3 = X | { a: string } & undefined;
33+
type X4 = X | string & number;
34+
type X5 = X | number & object;
35+
type X6 = X | symbol & string;
36+
type X7 = X | void & string;
37+
38+
// Repro from #31663
39+
40+
const x1 = { a: 'foo', b: 42 };
41+
const x2 = { a: 'foo', b: true };
42+
43+
declare let k: 'a' | 'b';
44+
45+
x1[k] = 'bar' as any; // Error
46+
~~~~~
47+
!!! error TS2322: Type 'any' is not assignable to type 'never'.
48+
x2[k] = 'bar' as any; // Error
49+
~~~~~
50+
!!! error TS2322: Type 'any' is not assignable to type 'never'.
51+
52+
const enum Tag1 {}
53+
const enum Tag2 {}
54+
55+
declare let s1: string & Tag1;
56+
declare let s2: string & Tag2;
57+
58+
declare let t1: string & Tag1 | undefined;
59+
declare let t2: string & Tag2 | undefined;
60+
61+
s1 = s2;
62+
s2 = s1;
63+
64+
t1 = t2;
65+
t2 = t1;
66+

Diff for: tests/baselines/reference/intersectionReduction.js

+56-7
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,69 @@
11
//// [intersectionReduction.ts]
2-
// @strict
3-
42
declare const sym1: unique symbol;
53
declare const sym2: unique symbol;
64

75
type T1 = string & 'a'; // 'a'
8-
type T2 = 'a' & string & 'b'; // 'a' & 'b'
6+
type T2 = 'a' & string & 'b'; // never
97
type T3 = number & 10; // 10
10-
type T4 = 10 & number & 20; // 10 & 20
8+
type T4 = 10 & number & 20; // never
119
type T5 = symbol & typeof sym1; // typeof sym1
12-
type T6 = typeof sym1 & symbol & typeof sym2; // typeof sym1 & typeof sym2
13-
type T7 = string & 'a' & number & 10 & symbol & typeof sym1; // 'a' & 10 & typeof sym1
10+
type T6 = typeof sym1 & symbol & typeof sym2; // never
11+
type T7 = string & 'a' & number & 10 & symbol & typeof sym1; // never
1412

1513
type T10 = string & ('a' | 'b'); // 'a' | 'b'
1614
type T11 = (string | number) & ('a' | 10); // 'a' | 10
15+
16+
type N1 = 'a' & 'b';
17+
type N2 = { a: string } & null;
18+
type N3 = { a: string } & undefined;
19+
type N4 = string & number;
20+
type N5 = number & object;
21+
type N6 = symbol & string;
22+
type N7 = void & string;
23+
24+
type X = { x: string };
25+
26+
type X1 = X | 'a' & 'b';
27+
type X2 = X | { a: string } & null;
28+
type X3 = X | { a: string } & undefined;
29+
type X4 = X | string & number;
30+
type X5 = X | number & object;
31+
type X6 = X | symbol & string;
32+
type X7 = X | void & string;
33+
34+
// Repro from #31663
35+
36+
const x1 = { a: 'foo', b: 42 };
37+
const x2 = { a: 'foo', b: true };
38+
39+
declare let k: 'a' | 'b';
40+
41+
x1[k] = 'bar' as any; // Error
42+
x2[k] = 'bar' as any; // Error
43+
44+
const enum Tag1 {}
45+
const enum Tag2 {}
46+
47+
declare let s1: string & Tag1;
48+
declare let s2: string & Tag2;
49+
50+
declare let t1: string & Tag1 | undefined;
51+
declare let t2: string & Tag2 | undefined;
52+
53+
s1 = s2;
54+
s2 = s1;
55+
56+
t1 = t2;
57+
t2 = t1;
1758

1859

1960
//// [intersectionReduction.js]
20-
// @strict
61+
// Repro from #31663
62+
var x1 = { a: 'foo', b: 42 };
63+
var x2 = { a: 'foo', b: true };
64+
x1[k] = 'bar'; // Error
65+
x2[k] = 'bar'; // Error
66+
s1 = s2;
67+
s2 = s1;
68+
t1 = t2;
69+
t2 = t1;

0 commit comments

Comments
 (0)