mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-16 07:13:45 -05:00
Only cache union/intersection relations once
This commit is contained in:
@@ -18346,30 +18346,17 @@ namespace ts {
|
||||
let result = Ternary.False;
|
||||
const saveErrorInfo = captureErrorCalculationState();
|
||||
|
||||
if (source.flags & TypeFlags.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) {
|
||||
// We skip caching when source or target is a union with no more than three constituents.
|
||||
result = (source.flags & TypeFlags.Union || target.flags & TypeFlags.Union) && getConstituentCount(source) * getConstituentCount(target) < 4 ?
|
||||
structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck) :
|
||||
recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck, recursionFlags);
|
||||
// The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle:
|
||||
// Source is instantiable (e.g. source has union or intersection constraint).
|
||||
// Source is an object, target is a union (e.g. { a, b: boolean } <=> { a, b: true } | { a, b: false }).
|
||||
// Source is an intersection, target is an object (e.g. { a } & { b } <=> { a, b }).
|
||||
// Source is an intersection, target is a union (e.g. { a } & { b: boolean } <=> { a, b: true } | { a, b: false }).
|
||||
// Source is an intersection, target instantiable (e.g. string & { tag } <=> T["a"] constrained to string & { tag }).
|
||||
if (!result && (source.flags & TypeFlags.Instantiable ||
|
||||
source.flags & TypeFlags.Object && target.flags & TypeFlags.Union ||
|
||||
source.flags & TypeFlags.Intersection && target.flags & (TypeFlags.Object | TypeFlags.Union | TypeFlags.Instantiable))) {
|
||||
if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) {
|
||||
resetErrorInfo(saveErrorInfo);
|
||||
}
|
||||
if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
|
||||
const skipCaching = source.flags & TypeFlags.Union && (source as UnionType).types.length < 4 && !(target.flags & TypeFlags.Union) ||
|
||||
target.flags & TypeFlags.Union && (target as UnionType).types.length < 4 && !(source.flags & TypeFlags.StructuredOrInstantiable);
|
||||
if (skipCaching) {
|
||||
result = unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState);
|
||||
}
|
||||
}
|
||||
else if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
|
||||
if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) {
|
||||
else if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) {
|
||||
resetErrorInfo(saveErrorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) {
|
||||
// The combined constraint of an intersection type is the intersection of the constraints of
|
||||
// the constituents. When an intersection type contains instantiable types with union type
|
||||
@@ -18606,6 +18593,51 @@ namespace ts {
|
||||
return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration;
|
||||
}
|
||||
|
||||
function unionOrIntersectionRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
||||
// Note that these checks are specifically ordered to produce correct results. In particular,
|
||||
// we need to deconstruct unions before intersections (because unions are always at the top),
|
||||
// and we need to handle "each" relations before "some" relations for the same kind of type.
|
||||
if (source.flags & TypeFlags.Union) {
|
||||
return relation === comparableRelation ?
|
||||
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck) :
|
||||
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck);
|
||||
}
|
||||
if (target.flags & TypeFlags.Union) {
|
||||
return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
|
||||
}
|
||||
if (target.flags & TypeFlags.Intersection) {
|
||||
return typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target);
|
||||
}
|
||||
// Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the
|
||||
// constraints of all non-primitive types in the source into a new intersection. We do this because the
|
||||
// intersection may further constrain the constraints of the non-primitive types. For example, given a type
|
||||
// parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't
|
||||
// appear to be comparable to '2'.
|
||||
if (relation === comparableRelation && target.flags & TypeFlags.Primitive) {
|
||||
const constraints = sameMap((source as IntersectionType).types, getBaseConstraintOrType);
|
||||
if (constraints !== (source as IntersectionType).types) {
|
||||
source = getIntersectionType(constraints);
|
||||
if (!(source.flags & TypeFlags.Intersection)) {
|
||||
return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check to see if any constituents of the intersection are immediately related to the target.
|
||||
//
|
||||
// Don't report errors though. Checking whether a constituent is related to the source is not actually
|
||||
// useful and leads to some confusing error messages. Instead it is better to let the below checks
|
||||
// take care of this, or to not elaborate at all. For instance,
|
||||
//
|
||||
// - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
|
||||
//
|
||||
// - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
|
||||
// than to report that 'D' is not assignable to 'A' or 'B'.
|
||||
//
|
||||
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
|
||||
// breaking the intersection apart.
|
||||
return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source);
|
||||
}
|
||||
|
||||
function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary {
|
||||
let result = Ternary.True;
|
||||
const sourceTypes = source.types;
|
||||
@@ -18903,49 +18935,23 @@ namespace ts {
|
||||
if (intersectionState & IntersectionState.PropertyCheck) {
|
||||
return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None);
|
||||
}
|
||||
if (intersectionState & IntersectionState.UnionIntersectionCheck) {
|
||||
// Note that these checks are specifically ordered to produce correct results. In particular,
|
||||
// we need to deconstruct unions before intersections (because unions are always at the top),
|
||||
// and we need to handle "each" relations before "some" relations for the same kind of type.
|
||||
if (source.flags & TypeFlags.Union) {
|
||||
return relation === comparableRelation ?
|
||||
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck) :
|
||||
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck);
|
||||
let result: Ternary;
|
||||
let originalErrorInfo: DiagnosticMessageChain | undefined;
|
||||
let varianceCheckFailed = false;
|
||||
const saveErrorInfo = captureErrorCalculationState();
|
||||
if (source.flags & TypeFlags.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) {
|
||||
result = unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState);
|
||||
// The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle:
|
||||
// Source is instantiable (e.g. source has union or intersection constraint).
|
||||
// Source is an object, target is a union (e.g. { a, b: boolean } <=> { a, b: true } | { a, b: false }).
|
||||
// Source is an intersection, target is an object (e.g. { a } & { b } <=> { a, b }).
|
||||
// Source is an intersection, target is a union (e.g. { a } & { b: boolean } <=> { a, b: true } | { a, b: false }).
|
||||
// Source is an intersection, target instantiable (e.g. string & { tag } <=> T["a"] constrained to string & { tag }).
|
||||
if (result || !(source.flags & TypeFlags.Instantiable ||
|
||||
source.flags & TypeFlags.Object && target.flags & TypeFlags.Union ||
|
||||
source.flags & TypeFlags.Intersection && target.flags & (TypeFlags.Object | TypeFlags.Union | TypeFlags.Instantiable))) {
|
||||
return result;
|
||||
}
|
||||
if (target.flags & TypeFlags.Union) {
|
||||
return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
|
||||
}
|
||||
if (target.flags & TypeFlags.Intersection) {
|
||||
return typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target);
|
||||
}
|
||||
// Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the
|
||||
// constraints of all non-primitive types in the source into a new intersection. We do this because the
|
||||
// intersection may further constrain the constraints of the non-primitive types. For example, given a type
|
||||
// parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't
|
||||
// appear to be comparable to '2'.
|
||||
if (relation === comparableRelation && target.flags & TypeFlags.Primitive) {
|
||||
const constraints = sameMap((source as IntersectionType).types, getBaseConstraintOrType);
|
||||
if (constraints !== (source as IntersectionType).types) {
|
||||
source = getIntersectionType(constraints);
|
||||
if (!(source.flags & TypeFlags.Intersection)) {
|
||||
return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check to see if any constituents of the intersection are immediately related to the target.
|
||||
//
|
||||
// Don't report errors though. Checking whether a constituent is related to the source is not actually
|
||||
// useful and leads to some confusing error messages. Instead it is better to let the below checks
|
||||
// take care of this, or to not elaborate at all. For instance,
|
||||
//
|
||||
// - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
|
||||
//
|
||||
// - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
|
||||
// than to report that 'D' is not assignable to 'A' or 'B'.
|
||||
//
|
||||
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
|
||||
// breaking the intersection apart.
|
||||
return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source);
|
||||
}
|
||||
const flags = source.flags & target.flags;
|
||||
if (relation === identityRelation && !(flags & TypeFlags.Object)) {
|
||||
@@ -18979,11 +18985,6 @@ namespace ts {
|
||||
return Ternary.False;
|
||||
}
|
||||
|
||||
let result: Ternary;
|
||||
let originalErrorInfo: DiagnosticMessageChain | undefined;
|
||||
let varianceCheckFailed = false;
|
||||
const saveErrorInfo = captureErrorCalculationState();
|
||||
|
||||
// We limit alias variance probing to only object and conditional types since their alias behavior
|
||||
// is more predictable than other, interned types, which may or may not have an alias depending on
|
||||
// the order in which things were checked.
|
||||
@@ -23338,10 +23339,6 @@ namespace ts {
|
||||
mapType(type, mapper);
|
||||
}
|
||||
|
||||
function getConstituentCount(type: Type) {
|
||||
return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1;
|
||||
}
|
||||
|
||||
function extractTypesOfKind(type: Type, kind: TypeFlags) {
|
||||
return filterType(type, t => (t.flags & kind) !== 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user