Fix nullability intersections in CFA and relations (#57724)

This commit is contained in:
Anders Hejlsberg 2024-03-11 14:00:56 -07:00 committed by GitHub
parent e24d886305
commit ef6a4ab5c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 394 additions and 3 deletions

View File

@ -21171,7 +21171,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (reduced !== type) {
return reduced;
}
if (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isEmptyAnonymousObjectType)) {
if (type.flags & TypeFlags.Intersection && shouldNormalizeIntersection(type as IntersectionType)) {
// Normalization handles cases like
// Partial<T>[K] & ({} | null) ==>
// Partial<T>[K] & {} | Partial<T>[K} & null ==>
// (T[K] | undefined) & {} | (T[K] | undefined) & null ==>
// T[K] & {} | undefined & {} | T[K] & null | undefined & null ==>
// T[K] & {} | T[K] & null
const normalizedTypes = sameMap(type.types, t => getNormalizedType(t, writing));
if (normalizedTypes !== type.types) {
return getIntersectionType(normalizedTypes);
@ -21180,6 +21186,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type;
}
function shouldNormalizeIntersection(type: IntersectionType) {
let hasInstantiable = false;
let hasNullableOrEmpty = false;
for (const t of type.types) {
hasInstantiable ||= !!(t.flags & TypeFlags.Instantiable);
hasNullableOrEmpty ||= !!(t.flags & TypeFlags.Nullable) || isEmptyAnonymousObjectType(t);
if (hasInstantiable && hasNullableOrEmpty) return true;
}
return false;
}
function getNormalizedTupleType(type: TupleTypeReference, writing: boolean): Type {
const elements = getElementTypes(type);
const normalizedElements = sameMap(elements, t => t.flags & TypeFlags.Simplifiable ? getSimplifiedType(t, writing) : t);
@ -26968,9 +26985,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (strictNullChecks) {
switch (facts) {
case TypeFacts.NEUndefined:
return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefined) ? getIntersectionType([t, hasTypeFacts(t, TypeFacts.EQNull) && !maybeTypeOfKind(reduced, TypeFlags.Null) ? getUnionType([emptyObjectType, nullType]) : emptyObjectType]) : t);
return removeNullableByIntersection(reduced, TypeFacts.EQUndefined, TypeFacts.EQNull, TypeFacts.IsNull, nullType);
case TypeFacts.NENull:
return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQNull) ? getIntersectionType([t, hasTypeFacts(t, TypeFacts.EQUndefined) && !maybeTypeOfKind(reduced, TypeFlags.Undefined) ? getUnionType([emptyObjectType, undefinedType]) : emptyObjectType]) : t);
return removeNullableByIntersection(reduced, TypeFacts.EQNull, TypeFacts.EQUndefined, TypeFacts.IsUndefined, undefinedType);
case TypeFacts.NEUndefinedOrNull:
case TypeFacts.Truthy:
return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefinedOrNull) ? getGlobalNonNullableTypeInstantiation(t) : t);
@ -26979,6 +26996,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return reduced;
}
function removeNullableByIntersection(type: Type, targetFacts: TypeFacts, otherFacts: TypeFacts, otherIncludesFacts: TypeFacts, otherType: Type) {
const facts = getTypeFacts(type, TypeFacts.EQUndefined | TypeFacts.EQNull | TypeFacts.IsUndefined | TypeFacts.IsNull);
// Simply return the type if it never compares equal to the target nullable.
if (!(facts & targetFacts)) {
return type;
}
// By default we intersect with a union of {} and the opposite nullable.
const emptyAndOtherUnion = getUnionType([emptyObjectType, otherType]);
// For each constituent type that can compare equal to the target nullable, intersect with the above union
// if the type doesn't already include the opppsite nullable and the constituent can compare equal to the
// opposite nullable; otherwise, just intersect with {}.
return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, !(facts & otherIncludesFacts) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t);
}
function recombineUnknownType(type: Type) {
return type === unknownUnionType ? unknownType : type;
}

View File

@ -0,0 +1,177 @@
//// [tests/cases/compiler/indexedAccessAndNullableNarrowing.ts] ////
=== indexedAccessAndNullableNarrowing.ts ===
function f1<T extends Record<string, any>, K extends keyof T>(x: T[K] | undefined) {
>f1 : Symbol(f1, Decl(indexedAccessAndNullableNarrowing.ts, 0, 0))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 0, 12))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 0, 42))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 0, 12))
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 0, 12))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 0, 42))
if (x === undefined) return;
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
>undefined : Symbol(undefined)
x; // T[K] & ({} | null)
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
if (x === undefined) return;
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
>undefined : Symbol(undefined)
x; // T[K] & ({} | null)
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 0, 62))
}
function f2<T extends Record<string, any>, K extends keyof T>(x: T[K] | null) {
>f2 : Symbol(f2, Decl(indexedAccessAndNullableNarrowing.ts, 5, 1))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 7, 12))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 7, 42))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 7, 12))
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 7, 12))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 7, 42))
if (x === null) return;
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
x; // T[K] & ({} | undefined)
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
if (x === null) return;
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
x; // T[K] & ({} | undefined)
>x : Symbol(x, Decl(indexedAccessAndNullableNarrowing.ts, 7, 62))
}
function f3<T, K extends keyof T>(t: T[K], p1: Partial<T>[K] & {}, p2: Partial<T>[K] & ({} | null)) {
>f3 : Symbol(f3, Decl(indexedAccessAndNullableNarrowing.ts, 12, 1))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 14, 14))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
>t : Symbol(t, Decl(indexedAccessAndNullableNarrowing.ts, 14, 34))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 14, 14))
>p1 : Symbol(p1, Decl(indexedAccessAndNullableNarrowing.ts, 14, 42))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 14, 14))
>p2 : Symbol(p2, Decl(indexedAccessAndNullableNarrowing.ts, 14, 66))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 14, 12))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 14, 14))
t = p1;
>t : Symbol(t, Decl(indexedAccessAndNullableNarrowing.ts, 14, 34))
>p1 : Symbol(p1, Decl(indexedAccessAndNullableNarrowing.ts, 14, 42))
t = p2;
>t : Symbol(t, Decl(indexedAccessAndNullableNarrowing.ts, 14, 34))
>p2 : Symbol(p2, Decl(indexedAccessAndNullableNarrowing.ts, 14, 66))
}
// https://github.com/microsoft/TypeScript/issues/57693
type AnyObject = Record<string, any>;
>AnyObject : Symbol(AnyObject, Decl(indexedAccessAndNullableNarrowing.ts, 17, 1))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
type State = AnyObject;
>State : Symbol(State, Decl(indexedAccessAndNullableNarrowing.ts, 21, 37))
>AnyObject : Symbol(AnyObject, Decl(indexedAccessAndNullableNarrowing.ts, 17, 1))
declare function hasOwnProperty<T extends AnyObject>(
>hasOwnProperty : Symbol(hasOwnProperty, Decl(indexedAccessAndNullableNarrowing.ts, 22, 23))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 24, 32))
>AnyObject : Symbol(AnyObject, Decl(indexedAccessAndNullableNarrowing.ts, 17, 1))
object: T,
>object : Symbol(object, Decl(indexedAccessAndNullableNarrowing.ts, 24, 53))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 24, 32))
prop: PropertyKey,
>prop : Symbol(prop, Decl(indexedAccessAndNullableNarrowing.ts, 25, 14))
>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --))
): prop is keyof T;
>prop : Symbol(prop, Decl(indexedAccessAndNullableNarrowing.ts, 25, 14))
>T : Symbol(T, Decl(indexedAccessAndNullableNarrowing.ts, 24, 32))
interface Store<S = State> {
>Store : Symbol(Store, Decl(indexedAccessAndNullableNarrowing.ts, 27, 19))
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 29, 16))
>State : Symbol(State, Decl(indexedAccessAndNullableNarrowing.ts, 21, 37))
setState<K extends keyof S>(key: K, value: S[K]): void;
>setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 30, 13))
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 29, 16))
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 30, 32))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 30, 13))
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 30, 39))
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 29, 16))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 30, 13))
}
export function syncStoreProp<
>syncStoreProp : Symbol(syncStoreProp, Decl(indexedAccessAndNullableNarrowing.ts, 31, 1))
S extends State,
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 33, 30))
>State : Symbol(State, Decl(indexedAccessAndNullableNarrowing.ts, 21, 37))
P extends Partial<S>,
>P : Symbol(P, Decl(indexedAccessAndNullableNarrowing.ts, 34, 20))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 33, 30))
K extends keyof S,
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 35, 25))
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 33, 30))
>(store: Store<S>, props: P, key: K) {
>store : Symbol(store, Decl(indexedAccessAndNullableNarrowing.ts, 37, 2))
>Store : Symbol(Store, Decl(indexedAccessAndNullableNarrowing.ts, 27, 19))
>S : Symbol(S, Decl(indexedAccessAndNullableNarrowing.ts, 33, 30))
>props : Symbol(props, Decl(indexedAccessAndNullableNarrowing.ts, 37, 18))
>P : Symbol(P, Decl(indexedAccessAndNullableNarrowing.ts, 34, 20))
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
>K : Symbol(K, Decl(indexedAccessAndNullableNarrowing.ts, 35, 25))
const value = hasOwnProperty(props, key) ? props[key] : undefined;
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
>hasOwnProperty : Symbol(hasOwnProperty, Decl(indexedAccessAndNullableNarrowing.ts, 22, 23))
>props : Symbol(props, Decl(indexedAccessAndNullableNarrowing.ts, 37, 18))
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
>props : Symbol(props, Decl(indexedAccessAndNullableNarrowing.ts, 37, 18))
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
>undefined : Symbol(undefined)
if (value === undefined) return;
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
>undefined : Symbol(undefined)
store.setState(key, value);
>store.setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
>store : Symbol(store, Decl(indexedAccessAndNullableNarrowing.ts, 37, 2))
>setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
if (value === undefined) return;
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
>undefined : Symbol(undefined)
store.setState(key, value);
>store.setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
>store : Symbol(store, Decl(indexedAccessAndNullableNarrowing.ts, 37, 2))
>setState : Symbol(Store.setState, Decl(indexedAccessAndNullableNarrowing.ts, 29, 28))
>key : Symbol(key, Decl(indexedAccessAndNullableNarrowing.ts, 37, 28))
>value : Symbol(value, Decl(indexedAccessAndNullableNarrowing.ts, 38, 9))
}

View File

@ -0,0 +1,136 @@
//// [tests/cases/compiler/indexedAccessAndNullableNarrowing.ts] ////
=== indexedAccessAndNullableNarrowing.ts ===
function f1<T extends Record<string, any>, K extends keyof T>(x: T[K] | undefined) {
>f1 : <T extends Record<string, any>, K extends keyof T>(x: T[K] | undefined) => void
>x : T[K] | undefined
if (x === undefined) return;
>x === undefined : boolean
>x : T[K] | undefined
>undefined : undefined
x; // T[K] & ({} | null)
>x : T[K] & ({} | null)
if (x === undefined) return;
>x === undefined : boolean
>x : T[K] & ({} | null)
>undefined : undefined
x; // T[K] & ({} | null)
>x : T[K] & ({} | null)
}
function f2<T extends Record<string, any>, K extends keyof T>(x: T[K] | null) {
>f2 : <T extends Record<string, any>, K extends keyof T>(x: T[K] | null) => void
>x : T[K] | null
if (x === null) return;
>x === null : boolean
>x : T[K] | null
x; // T[K] & ({} | undefined)
>x : T[K] & ({} | undefined)
if (x === null) return;
>x === null : boolean
>x : T[K] & ({} | undefined)
x; // T[K] & ({} | undefined)
>x : T[K] & ({} | undefined)
}
function f3<T, K extends keyof T>(t: T[K], p1: Partial<T>[K] & {}, p2: Partial<T>[K] & ({} | null)) {
>f3 : <T, K extends keyof T>(t: T[K], p1: Partial<T>[K] & {}, p2: Partial<T>[K] & ({} | null)) => void
>t : T[K]
>p1 : Partial<T>[K] & {}
>p2 : Partial<T>[K] & ({} | null)
t = p1;
>t = p1 : Partial<T>[K] & {}
>t : T[K]
>p1 : Partial<T>[K] & {}
t = p2;
>t = p2 : Partial<T>[K] & ({} | null)
>t : T[K]
>p2 : Partial<T>[K] & ({} | null)
}
// https://github.com/microsoft/TypeScript/issues/57693
type AnyObject = Record<string, any>;
>AnyObject : { [x: string]: any; }
type State = AnyObject;
>State : AnyObject
declare function hasOwnProperty<T extends AnyObject>(
>hasOwnProperty : <T extends AnyObject>(object: T, prop: PropertyKey) => prop is keyof T
object: T,
>object : T
prop: PropertyKey,
>prop : PropertyKey
): prop is keyof T;
interface Store<S = State> {
setState<K extends keyof S>(key: K, value: S[K]): void;
>setState : <K extends keyof S>(key: K, value: S[K]) => void
>key : K
>value : S[K]
}
export function syncStoreProp<
>syncStoreProp : <S extends AnyObject, P extends Partial<S>, K extends keyof S>(store: Store<S>, props: P, key: K) => void
S extends State,
P extends Partial<S>,
K extends keyof S,
>(store: Store<S>, props: P, key: K) {
>store : Store<S>
>props : P
>key : K
const value = hasOwnProperty(props, key) ? props[key] : undefined;
>value : P[K] | undefined
>hasOwnProperty(props, key) ? props[key] : undefined : P[K] | undefined
>hasOwnProperty(props, key) : boolean
>hasOwnProperty : <T extends AnyObject>(object: T, prop: PropertyKey) => prop is keyof T
>props : P
>key : string | number | symbol
>props[key] : P[K]
>props : P
>key : K
>undefined : undefined
if (value === undefined) return;
>value === undefined : boolean
>value : P[K] | undefined
>undefined : undefined
store.setState(key, value);
>store.setState(key, value) : void
>store.setState : <K_1 extends keyof S>(key: K_1, value: S[K_1]) => void
>store : Store<S>
>setState : <K_1 extends keyof S>(key: K_1, value: S[K_1]) => void
>key : K
>value : P[K] & ({} | null)
if (value === undefined) return;
>value === undefined : boolean
>value : P[K] & ({} | null)
>undefined : undefined
store.setState(key, value);
>store.setState(key, value) : void
>store.setState : <K_1 extends keyof S>(key: K_1, value: S[K_1]) => void
>store : Store<S>
>setState : <K_1 extends keyof S>(key: K_1, value: S[K_1]) => void
>key : K
>value : P[K] & ({} | null)
}

View File

@ -0,0 +1,47 @@
// @strict: true
// @noEmit: true
function f1<T extends Record<string, any>, K extends keyof T>(x: T[K] | undefined) {
if (x === undefined) return;
x; // T[K] & ({} | null)
if (x === undefined) return;
x; // T[K] & ({} | null)
}
function f2<T extends Record<string, any>, K extends keyof T>(x: T[K] | null) {
if (x === null) return;
x; // T[K] & ({} | undefined)
if (x === null) return;
x; // T[K] & ({} | undefined)
}
function f3<T, K extends keyof T>(t: T[K], p1: Partial<T>[K] & {}, p2: Partial<T>[K] & ({} | null)) {
t = p1;
t = p2;
}
// https://github.com/microsoft/TypeScript/issues/57693
type AnyObject = Record<string, any>;
type State = AnyObject;
declare function hasOwnProperty<T extends AnyObject>(
object: T,
prop: PropertyKey,
): prop is keyof T;
interface Store<S = State> {
setState<K extends keyof S>(key: K, value: S[K]): void;
}
export function syncStoreProp<
S extends State,
P extends Partial<S>,
K extends keyof S,
>(store: Store<S>, props: P, key: K) {
const value = hasOwnProperty(props, key) ? props[key] : undefined;
if (value === undefined) return;
store.setState(key, value);
if (value === undefined) return;
store.setState(key, value);
}