Revise discriminateTypeByDiscriminableItems function (#53709)

Co-authored-by: Maria Solano <mariasolano@microsoft.com>
This commit is contained in:
Anders Hejlsberg
2023-04-12 07:25:58 -07:00
committed by GitHub
co-authored by Maria Solano
parent 2db688e36f
commit bec204c844
6 changed files with 82 additions and 62 deletions
+31 -30
View File
@@ -22609,41 +22609,41 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) {
return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) ||
return findMatchingDiscriminantType(source, target, isRelatedTo) ||
findMatchingTypeReferenceOrTypeAliasReference(source, target) ||
findBestTypeForObjectLiteral(source, target) ||
findBestTypeForInvokable(source, target) ||
findMostOverlappyType(source, target);
}
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined;
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type;
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) {
// undefined=unknown, true=discriminated, false=not discriminated
// The state of each type progresses from left to right. Discriminated types stop at 'true'.
const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[];
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary) {
const types = target.types;
const include: Ternary[] = types.map(t => t.flags & TypeFlags.Primitive ? Ternary.False : Ternary.True);
for (const [getDiscriminatingType, propertyName] of discriminators) {
const targetProp = getUnionOrIntersectionProperty(target, propertyName);
if (skipPartial && targetProp && getCheckFlags(targetProp) & CheckFlags.ReadPartial) {
continue;
// If the remaining target types include at least one with a matching discriminant, eliminate those that
// have non-matching discriminants. This ensures that we ignore erroneous discriminators and gradually
// refine the target set without eliminating every constituent (which would lead to `never`).
let matched = false;
for (let i = 0; i < types.length; i++) {
if (include[i]) {
const targetType = getTypeOfPropertyOfType(types[i], propertyName);
if (targetType && related(getDiscriminatingType(), targetType)) {
matched = true;
}
else {
include[i] = Ternary.Maybe;
}
}
}
let i = 0;
for (const type of target.types) {
const targetType = getTypeOfPropertyOfType(type, propertyName);
if (targetType && related(getDiscriminatingType(), targetType)) {
discriminable[i] = discriminable[i] === undefined ? true : discriminable[i];
// Turn each Ternary.Maybe into Ternary.False if there was a match. Otherwise, revert to Ternary.True.
for (let i = 0; i < types.length; i++) {
if (include[i] === Ternary.Maybe) {
include[i] = matched ? Ternary.False : Ternary.True;
}
else {
discriminable[i] = false;
}
i++;
}
}
const match = discriminable.indexOf(/*searchElement*/ true);
if (match === -1) {
return defaultValue;
}
return getUnionType(target.types.filter((_, index) => discriminable[index]));
const filtered = contains(include, Ternary.False) ? getUnionType(types.filter((_, i) => include[i])) : target;
return filtered.flags & TypeFlags.Never ? target : filtered;
}
/**
@@ -29290,8 +29290,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
s => [() => undefinedType, s.escapedName] as [() => Type, __String]
)
),
isTypeAssignableTo,
contextualType
isTypeAssignableTo
);
}
@@ -29307,8 +29306,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
s => [() => undefinedType, s.escapedName] as [() => Type, __String]
)
),
isTypeAssignableTo,
contextualType
isTypeAssignableTo
);
}
@@ -48753,7 +48751,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly
function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary, skipPartial?: boolean) {
function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary) {
if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) {
const match = getMatchingUnionConstituentForType(target as UnionType, source);
if (match) {
@@ -48763,7 +48761,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (sourceProperties) {
const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target);
if (sourcePropertiesFiltered) {
return discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo, /*defaultValue*/ undefined, skipPartial);
const discriminated = discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo);
if (discriminated !== target) {
return discriminated;
}
}
}
}