Skip to content

Commit ef6a4ab

Browse files
authored
Fix nullability intersections in CFA and relations (#57724)
1 parent e24d886 commit ef6a4ab

File tree

4 files changed

+394
-3
lines changed

4 files changed

+394
-3
lines changed

src/compiler/checker.ts

+34-3
Original file line numberDiff line numberDiff line change
@@ -21171,7 +21171,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2117121171
if (reduced !== type) {
2117221172
return reduced;
2117321173
}
21174-
if (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isEmptyAnonymousObjectType)) {
21174+
if (type.flags & TypeFlags.Intersection && shouldNormalizeIntersection(type as IntersectionType)) {
21175+
// Normalization handles cases like
21176+
// Partial<T>[K] & ({} | null) ==>
21177+
// Partial<T>[K] & {} | Partial<T>[K} & null ==>
21178+
// (T[K] | undefined) & {} | (T[K] | undefined) & null ==>
21179+
// T[K] & {} | undefined & {} | T[K] & null | undefined & null ==>
21180+
// T[K] & {} | T[K] & null
2117521181
const normalizedTypes = sameMap(type.types, t => getNormalizedType(t, writing));
2117621182
if (normalizedTypes !== type.types) {
2117721183
return getIntersectionType(normalizedTypes);
@@ -21180,6 +21186,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2118021186
return type;
2118121187
}
2118221188

21189+
function shouldNormalizeIntersection(type: IntersectionType) {
21190+
let hasInstantiable = false;
21191+
let hasNullableOrEmpty = false;
21192+
for (const t of type.types) {
21193+
hasInstantiable ||= !!(t.flags & TypeFlags.Instantiable);
21194+
hasNullableOrEmpty ||= !!(t.flags & TypeFlags.Nullable) || isEmptyAnonymousObjectType(t);
21195+
if (hasInstantiable && hasNullableOrEmpty) return true;
21196+
}
21197+
return false;
21198+
}
21199+
2118321200
function getNormalizedTupleType(type: TupleTypeReference, writing: boolean): Type {
2118421201
const elements = getElementTypes(type);
2118521202
const normalizedElements = sameMap(elements, t => t.flags & TypeFlags.Simplifiable ? getSimplifiedType(t, writing) : t);
@@ -26968,9 +26985,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2696826985
if (strictNullChecks) {
2696926986
switch (facts) {
2697026987
case TypeFacts.NEUndefined:
26971-
return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefined) ? getIntersectionType([t, hasTypeFacts(t, TypeFacts.EQNull) && !maybeTypeOfKind(reduced, TypeFlags.Null) ? getUnionType([emptyObjectType, nullType]) : emptyObjectType]) : t);
26988+
return removeNullableByIntersection(reduced, TypeFacts.EQUndefined, TypeFacts.EQNull, TypeFacts.IsNull, nullType);
2697226989
case TypeFacts.NENull:
26973-
return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQNull) ? getIntersectionType([t, hasTypeFacts(t, TypeFacts.EQUndefined) && !maybeTypeOfKind(reduced, TypeFlags.Undefined) ? getUnionType([emptyObjectType, undefinedType]) : emptyObjectType]) : t);
26990+
return removeNullableByIntersection(reduced, TypeFacts.EQNull, TypeFacts.EQUndefined, TypeFacts.IsUndefined, undefinedType);
2697426991
case TypeFacts.NEUndefinedOrNull:
2697526992
case TypeFacts.Truthy:
2697626993
return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefinedOrNull) ? getGlobalNonNullableTypeInstantiation(t) : t);
@@ -26979,6 +26996,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2697926996
return reduced;
2698026997
}
2698126998

26999+
function removeNullableByIntersection(type: Type, targetFacts: TypeFacts, otherFacts: TypeFacts, otherIncludesFacts: TypeFacts, otherType: Type) {
27000+
const facts = getTypeFacts(type, TypeFacts.EQUndefined | TypeFacts.EQNull | TypeFacts.IsUndefined | TypeFacts.IsNull);
27001+
// Simply return the type if it never compares equal to the target nullable.
27002+
if (!(facts & targetFacts)) {
27003+
return type;
27004+
}
27005+
// By default we intersect with a union of {} and the opposite nullable.
27006+
const emptyAndOtherUnion = getUnionType([emptyObjectType, otherType]);
27007+
// For each constituent type that can compare equal to the target nullable, intersect with the above union
27008+
// if the type doesn't already include the opppsite nullable and the constituent can compare equal to the
27009+
// opposite nullable; otherwise, just intersect with {}.
27010+
return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, !(facts & otherIncludesFacts) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t);
27011+
}
27012+
2698227013
function recombineUnknownType(type: Type) {
2698327014
return type === unknownUnionType ? unknownType : type;
2698427015
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//// [tests/cases/compiler/indexedAccessAndNullableNarrowing.ts] ////
2+
3+
=== indexedAccessAndNullableNarrowing.ts ===
4+
function f1<T extends Record<string, any>, K extends keyof T>(x: T[K] | undefined) {
5+
>f1 : Symbol(f1, Decl(indexedAccessAndNullableNarrowing.ts, 0, 0))
6+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 0, 12))
7+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
8+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 0, 42))
9+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 0, 12))
10+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
11+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 0, 12))
12+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 0, 42))
13+
14+
if (x === undefined) return;
15+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
16+
>undefined : Symbol(undefined)
17+
18+
x; // T[K] & ({} | null)
19+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
20+
21+
if (x === undefined) return;
22+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
23+
>undefined : Symbol(undefined)
24+
25+
x; // T[K] & ({} | null)
26+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
27+
}
28+
29+
function f2<T extends Record<string, any>, K extends keyof T>(x: T[K] | null) {
30+
>f2 : Symbol(f2, Decl(indexedAccessAndNullableNarrowing.ts, 5, 1))
31+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 7, 12))
32+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
33+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 7, 42))
34+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 7, 12))
35+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
36+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 7, 12))
37+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 7, 42))
38+
39+
if (x === null) return;
40+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
41+
42+
x; // T[K] & ({} | undefined)
43+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
44+
45+
if (x === null) return;
46+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
47+
48+
x; // T[K] & ({} | undefined)
49+
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
50+
}
51+
52+
function f3<T, K extends keyof T>(t: T[K], p1: Partial<T>[K] & {}, p2: Partial<T>[K] & ({} | null)) {
53+
>f3 : Symbol(f3, Decl(indexedAccessAndNullableNarrowing.ts, 12, 1))
54+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
55+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 14, 14))
56+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
57+
>t : Symbol(t, Decl(indexedAccessAndNullableNarrowing.ts, 14, 34))
58+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
59+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 14, 14))
60+
>p1 : Symbol(p1, Decl(indexedAccessAndNullableNarrowing.ts, 14, 42))
61+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
62+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
63+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 14, 14))
64+
>p2 : Symbol(p2, Decl(indexedAccessAndNullableNarrowing.ts, 14, 66))
65+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
66+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
67+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 14, 14))
68+
69+
t = p1;
70+
>t : Symbol(t, Decl(indexedAccessAndNullableNarrowing.ts, 14, 34))
71+
>p1 : Symbol(p1, Decl(indexedAccessAndNullableNarrowing.ts, 14, 42))
72+
73+
t = p2;
74+
>t : Symbol(t, Decl(indexedAccessAndNullableNarrowing.ts, 14, 34))
75+
>p2 : Symbol(p2, Decl(indexedAccessAndNullableNarrowing.ts, 14, 66))
76+
}
77+
78+
// https://github.com/microsoft/TypeScript/issues/57693
79+
80+
type AnyObject = Record<string, any>;
81+
>AnyObject : Symbol(AnyObject, Decl(indexedAccessAndNullableNarrowing.ts, 17, 1))
82+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
83+
84+
type State = AnyObject;
85+
>State : Symbol(State, Decl(indexedAccessAndNullableNarrowing.ts, 21, 37))
86+
>AnyObject : Symbol(AnyObject, Decl(indexedAccessAndNullableNarrowing.ts, 17, 1))
87+
88+
declare function hasOwnProperty<T extends AnyObject>(
89+
>hasOwnProperty : Symbol(hasOwnProperty, Decl(indexedAccessAndNullableNarrowing.ts, 22, 23))
90+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 24, 32))
91+
>AnyObject : Symbol(AnyObject, Decl(indexedAccessAndNullableNarrowing.ts, 17, 1))
92+
93+
object: T,
94+
>object : Symbol(object, Decl(indexedAccessAndNullableNarrowing.ts, 24, 53))
95+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 24, 32))
96+
97+
prop: PropertyKey,
98+
>prop : Symbol(prop, Decl(indexedAccessAndNullableNarrowing.ts, 25, 14))
99+
>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --))
100+
101+
): prop is keyof T;
102+
>prop : Symbol(prop, Decl(indexedAccessAndNullableNarrowing.ts, 25, 14))
103+
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 24, 32))
104+
105+
interface Store<S = State> {
106+
>Store : Symbol(Store, Decl(indexedAccessAndNullableNarrowing.ts, 27, 19))
107+
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 29, 16))
108+
>State : Symbol(State, Decl(indexedAccessAndNullableNarrowing.ts, 21, 37))
109+
110+
setState<K extends keyof S>(key: K, value: S[K]): void;
111+
>setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
112+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 30, 13))
113+
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 29, 16))
114+
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 30, 32))
115+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 30, 13))
116+
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 30, 39))
117+
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 29, 16))
118+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 30, 13))
119+
}
120+
121+
export function syncStoreProp<
122+
>syncStoreProp : Symbol(syncStoreProp, Decl(indexedAccessAndNullableNarrowing.ts, 31, 1))
123+
124+
S extends State,
125+
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 33, 30))
126+
>State : Symbol(State, Decl(indexedAccessAndNullableNarrowing.ts, 21, 37))
127+
128+
P extends Partial<S>,
129+
>P : Symbol(P, Decl(indexedAccessAndNullableNarrowing.ts, 34, 20))
130+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
131+
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 33, 30))
132+
133+
K extends keyof S,
134+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 35, 25))
135+
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 33, 30))
136+
137+
>(store: Store<S>, props: P, key: K) {
138+
>store : Symbol(store, Decl(indexedAccessAndNullableNarrowing.ts, 37, 2))
139+
>Store : Symbol(Store, Decl(indexedAccessAndNullableNarrowing.ts, 27, 19))
140+
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 33, 30))
141+
>props : Symbol(props, Decl(indexedAccessAndNullableNarrowing.ts, 37, 18))
142+
>P : Symbol(P, Decl(indexedAccessAndNullableNarrowing.ts, 34, 20))
143+
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
144+
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 35, 25))
145+
146+
const value = hasOwnProperty(props, key) ? props[key] : undefined;
147+
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
148+
>hasOwnProperty : Symbol(hasOwnProperty, Decl(indexedAccessAndNullableNarrowing.ts, 22, 23))
149+
>props : Symbol(props, Decl(indexedAccessAndNullableNarrowing.ts, 37, 18))
150+
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
151+
>props : Symbol(props, Decl(indexedAccessAndNullableNarrowing.ts, 37, 18))
152+
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
153+
>undefined : Symbol(undefined)
154+
155+
if (value === undefined) return;
156+
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
157+
>undefined : Symbol(undefined)
158+
159+
store.setState(key, value);
160+
>store.setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
161+
>store : Symbol(store, Decl(indexedAccessAndNullableNarrowing.ts, 37, 2))
162+
>setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
163+
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
164+
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
165+
166+
if (value === undefined) return;
167+
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
168+
>undefined : Symbol(undefined)
169+
170+
store.setState(key, value);
171+
>store.setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
172+
>store : Symbol(store, Decl(indexedAccessAndNullableNarrowing.ts, 37, 2))
173+
>setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
174+
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
175+
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
176+
}
177+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//// [tests/cases/compiler/indexedAccessAndNullableNarrowing.ts] ////
2+
3+
=== indexedAccessAndNullableNarrowing.ts ===
4+
function f1<T extends Record<string, any>, K extends keyof T>(x: T[K] | undefined) {
5+
>f1 : <T extends Record<string, any>, K extends keyof T>(x: T[K] | undefined) => void
6+
>x : T[K] | undefined
7+
8+
if (x === undefined) return;
9+
>x === undefined : boolean
10+
>x : T[K] | undefined
11+
>undefined : undefined
12+
13+
x; // T[K] & ({} | null)
14+
>x : T[K] & ({} | null)
15+
16+
if (x === undefined) return;
17+
>x === undefined : boolean
18+
>x : T[K] & ({} | null)
19+
>undefined : undefined
20+
21+
x; // T[K] & ({} | null)
22+
>x : T[K] & ({} | null)
23+
}
24+
25+
function f2<T extends Record<string, any>, K extends keyof T>(x: T[K] | null) {
26+
>f2 : <T extends Record<string, any>, K extends keyof T>(x: T[K] | null) => void
27+
>x : T[K] | null
28+
29+
if (x === null) return;
30+
>x === null : boolean
31+
>x : T[K] | null
32+
33+
x; // T[K] & ({} | undefined)
34+
>x : T[K] & ({} | undefined)
35+
36+
if (x === null) return;
37+
>x === null : boolean
38+
>x : T[K] & ({} | undefined)
39+
40+
x; // T[K] & ({} | undefined)
41+
>x : T[K] & ({} | undefined)
42+
}
43+
44+
function f3<T, K extends keyof T>(t: T[K], p1: Partial<T>[K] & {}, p2: Partial<T>[K] & ({} | null)) {
45+
>f3 : <T, K extends keyof T>(t: T[K], p1: Partial<T>[K] & {}, p2: Partial<T>[K] & ({} | null)) => void
46+
>t : T[K]
47+
>p1 : Partial<T>[K] & {}
48+
>p2 : Partial<T>[K] & ({} | null)
49+
50+
t = p1;
51+
>t = p1 : Partial<T>[K] & {}
52+
>t : T[K]
53+
>p1 : Partial<T>[K] & {}
54+
55+
t = p2;
56+
>t = p2 : Partial<T>[K] & ({} | null)
57+
>t : T[K]
58+
>p2 : Partial<T>[K] & ({} | null)
59+
}
60+
61+
// https://github.com/microsoft/TypeScript/issues/57693
62+
63+
type AnyObject = Record<string, any>;
64+
>AnyObject : { [x: string]: any; }
65+
66+
type State = AnyObject;
67+
>State : AnyObject
68+
69+
declare function hasOwnProperty<T extends AnyObject>(
70+
>hasOwnProperty : <T extends AnyObject>(object: T, prop: PropertyKey) => prop is keyof T
71+
72+
object: T,
73+
>object : T
74+
75+
prop: PropertyKey,
76+
>prop : PropertyKey
77+
78+
): prop is keyof T;
79+
80+
interface Store<S = State> {
81+
setState<K extends keyof S>(key: K, value: S[K]): void;
82+
>setState : <K extends keyof S>(key: K, value: S[K]) => void
83+
>key : K
84+
>value : S[K]
85+
}
86+
87+
export function syncStoreProp<
88+
>syncStoreProp : <S extends AnyObject, P extends Partial<S>, K extends keyof S>(store: Store<S>, props: P, key: K) => void
89+
90+
S extends State,
91+
P extends Partial<S>,
92+
K extends keyof S,
93+
>(store: Store<S>, props: P, key: K) {
94+
>store : Store<S>
95+
>props : P
96+
>key : K
97+
98+
const value = hasOwnProperty(props, key) ? props[key] : undefined;
99+
>value : P[K] | undefined
100+
>hasOwnProperty(props, key) ? props[key] : undefined : P[K] | undefined
101+
>hasOwnProperty(props, key) : boolean
102+
>hasOwnProperty : <T extends AnyObject>(object: T, prop: PropertyKey) => prop is keyof T
103+
>props : P
104+
>key : string | number | symbol
105+
>props[key] : P[K]
106+
>props : P
107+
>key : K
108+
>undefined : undefined
109+
110+
if (value === undefined) return;
111+
>value === undefined : boolean
112+
>value : P[K] | undefined
113+
>undefined : undefined
114+
115+
store.setState(key, value);
116+
>store.setState(key, value) : void
117+
>store.setState : <K_1 extends keyof S>(key: K_1, value: S[K_1]) => void
118+
>store : Store<S>
119+
>setState : <K_1 extends keyof S>(key: K_1, value: S[K_1]) => void
120+
>key : K
121+
>value : P[K] & ({} | null)
122+
123+
if (value === undefined) return;
124+
>value === undefined : boolean
125+
>value : P[K] & ({} | null)
126+
>undefined : undefined
127+
128+
store.setState(key, value);
129+
>store.setState(key, value) : void
130+
>store.setState : <K_1 extends keyof S>(key: K_1, value: S[K_1]) => void
131+
>store : Store<S>
132+
>setState : <K_1 extends keyof S>(key: K_1, value: S[K_1]) => void
133+
>key : K
134+
>value : P[K] & ({} | null)
135+
}
136+

0 commit comments

Comments
 (0)