Skip to content

Commit 868e854

Browse files
committed
Cache complex union/intersection relations
1 parent eb105ef commit 868e854

File tree

1 file changed

+44
-32
lines changed

1 file changed

+44
-32
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ namespace ts {
196196
Source = 1 << 0,
197197
Target = 1 << 1,
198198
PropertyCheck = 1 << 2,
199-
InPropertyCheck = 1 << 3,
199+
UnionIntersectionCheck = 1 << 3,
200+
InPropertyCheck = 1 << 4,
200201
}
201202

202203
const enum MappedTypeModifiers {
@@ -15621,38 +15622,14 @@ namespace ts {
1562115622
// Note that these checks are specifically ordered to produce correct results. In particular,
1562215623
// we need to deconstruct unions before intersections (because unions are always at the top),
1562315624
// and we need to handle "each" relations before "some" relations for the same kind of type.
15624-
if (source.flags & TypeFlags.Union) {
15625-
result = relation === comparableRelation ?
15626-
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) :
15627-
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState);
15625+
if (source.flags & TypeFlags.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) {
15626+
result = getConstituentCount(source) * getConstituentCount(target) >= 4 ?
15627+
recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck) :
15628+
structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck);
1562815629
}
15629-
else {
15630-
if (target.flags & TypeFlags.Union) {
15631-
result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
15632-
}
15633-
else if (target.flags & TypeFlags.Intersection) {
15634-
result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target);
15635-
}
15636-
else if (source.flags & TypeFlags.Intersection) {
15637-
// Check to see if any constituents of the intersection are immediately related to the target.
15638-
//
15639-
// Don't report errors though. Checking whether a constituent is related to the source is not actually
15640-
// useful and leads to some confusing error messages. Instead it is better to let the below checks
15641-
// take care of this, or to not elaborate at all. For instance,
15642-
//
15643-
// - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
15644-
//
15645-
// - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
15646-
// than to report that 'D' is not assignable to 'A' or 'B'.
15647-
//
15648-
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
15649-
// breaking the intersection apart.
15650-
result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false, IntersectionState.Source);
15651-
}
15652-
if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) {
15653-
if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState)) {
15654-
resetErrorInfo(saveErrorInfo);
15655-
}
15630+
if (!result && !(source.flags & TypeFlags.Union) && (source.flags & (TypeFlags.StructuredOrInstantiable) || target.flags & TypeFlags.StructuredOrInstantiable)) {
15631+
if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState)) {
15632+
resetErrorInfo(saveErrorInfo);
1565615633
}
1565715634
}
1565815635
if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) {
@@ -16080,6 +16057,37 @@ namespace ts {
1608016057
if (intersectionState & IntersectionState.PropertyCheck) {
1608116058
return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None);
1608216059
}
16060+
if (intersectionState & IntersectionState.UnionIntersectionCheck) {
16061+
// Note that these checks are specifically ordered to produce correct results. In particular,
16062+
// we need to deconstruct unions before intersections (because unions are always at the top),
16063+
// and we need to handle "each" relations before "some" relations for the same kind of type.
16064+
if (source.flags & TypeFlags.Union) {
16065+
return relation === comparableRelation ?
16066+
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck) :
16067+
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck);
16068+
}
16069+
if (target.flags & TypeFlags.Union) {
16070+
return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
16071+
}
16072+
if (target.flags & TypeFlags.Intersection) {
16073+
return typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target);
16074+
}
16075+
// Source is an intersection. Check to see if any constituents of the intersection are immediately related
16076+
// to the target.
16077+
//
16078+
// Don't report errors though. Checking whether a constituent is related to the source is not actually
16079+
// useful and leads to some confusing error messages. Instead it is better to let the below checks
16080+
// take care of this, or to not elaborate at all. For instance,
16081+
//
16082+
// - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
16083+
//
16084+
// - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
16085+
// than to report that 'D' is not assignable to 'A' or 'B'.
16086+
//
16087+
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
16088+
// breaking the intersection apart.
16089+
return someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false, IntersectionState.Source);
16090+
}
1608316091
const flags = source.flags & target.flags;
1608416092
if (relation === identityRelation && !(flags & TypeFlags.Object)) {
1608516093
if (flags & TypeFlags.Index) {
@@ -19645,6 +19653,10 @@ namespace ts {
1964519653
return mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal);
1964619654
}
1964719655

19656+
function getConstituentCount(type: Type) {
19657+
return type.flags & TypeFlags.UnionOrIntersection ? (<UnionOrIntersectionType>type).types.length : 1;
19658+
}
19659+
1964819660
function extractTypesOfKind(type: Type, kind: TypeFlags) {
1964919661
return filterType(type, t => (t.flags & kind) !== 0);
1965019662
}

0 commit comments

Comments
 (0)