mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-29 16:29:19 -05:00
Fix nullability intersections in CFA and relations (#57724)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user