Merge pull request #23672 from Microsoft/intersectionWithUnionConstraint

Type relationships for intersections with union constraints
This commit is contained in:
Anders Hejlsberg
2018-04-27 15:53:00 -07:00
committed by GitHub
16 changed files with 506 additions and 28 deletions
+72 -3
View File
@@ -6470,6 +6470,47 @@ namespace ts {
return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type);
}
function getUnionConstraintOfIntersection(type: IntersectionType, targetIsUnion: boolean) {
let constraints: Type[];
let hasDisjointDomainType = false;
for (const t of type.types) {
if (t.flags & TypeFlags.Instantiable) {
// We keep following constraints as long as we have an instantiable type that is known
// not to be circular or infinite (hence we stop on index access types).
let constraint = getConstraintOfType(t);
while (constraint && constraint.flags & (TypeFlags.TypeParameter | TypeFlags.Index | TypeFlags.Conditional)) {
constraint = getConstraintOfType(constraint);
}
if (constraint) {
// A constraint that isn't a union type implies that the final type would be a non-union
// type as well. Since non-union constraints are of no interest, we can exit here.
if (!(constraint.flags & TypeFlags.Union)) {
return undefined;
}
constraints = append(constraints, constraint);
}
}
else if (t.flags & TypeFlags.DisjointDomains) {
hasDisjointDomainType = true;
}
}
// If the target is a union type or if we are intersecting with types belonging to one of the
// disjoint domans, we may end up producing a constraint that hasn't been examined before.
if (constraints && (targetIsUnion || hasDisjointDomainType)) {
if (hasDisjointDomainType) {
// We add any types belong to one of the disjoint domans because they might cause the final
// intersection operation to reduce the union constraints.
for (const t of type.types) {
if (t.flags & TypeFlags.DisjointDomains) {
constraints = append(constraints, t);
}
}
}
return getIntersectionType(constraints);
}
return undefined;
}
function getBaseConstraintOfInstantiableNonPrimitiveUnionOrIntersection(type: Type) {
if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection)) {
const constraint = getResolvedBaseConstraint(<InstantiableType | UnionOrIntersectionType>type);
@@ -7951,8 +7992,14 @@ namespace ts {
return binarySearch(types, type, getTypeId, compareValues) >= 0;
}
// Return true if the given intersection type contains (a) more than one unit type or (b) an object
// type and a nullable type (null or undefined).
// Return true if the given intersection type contains
// more than one unit type or,
// an object type and a nullable type (null or undefined), or
// a string-like type and a type known to be non-string-like, or
// a number-like type and a type known to be non-number-like, or
// a symbol-like type and a type known to be non-symbol-like, or
// a void-like type and a type known to be non-void-like, or
// a non-primitive type and a type known to be primitive.
function isEmptyIntersectionType(type: IntersectionType) {
let combined: TypeFlags = 0;
for (const t of type.types) {
@@ -7960,7 +8007,12 @@ namespace ts {
return true;
}
combined |= t.flags;
if (combined & TypeFlags.Nullable && combined & (TypeFlags.Object | TypeFlags.NonPrimitive)) {
if (combined & TypeFlags.Nullable && combined & (TypeFlags.Object | TypeFlags.NonPrimitive) ||
combined & TypeFlags.NonPrimitive && combined & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) ||
combined & TypeFlags.StringLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) ||
combined & TypeFlags.NumberLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) ||
combined & TypeFlags.ESSymbolLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) ||
combined & TypeFlags.VoidLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) {
return true;
}
}
@@ -10180,6 +10232,23 @@ namespace ts {
}
}
}
if (!result && source.flags & TypeFlags.Intersection) {
// 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
// constraints, there are situations where we need to examine the combined constraint. One is
// when the target is a union type. Another is when the intersection contains types belonging
// to one of the disjoint domains. For example, given type variables T and U, each with the
// constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and
// we need to check this constraint against a union on the target side. Also, given a type
// variable V constrained to 'string | number', 'V & number' has a combined constraint of
// 'string & number | number & number' which reduces to just 'number'.
const constraint = getUnionConstraintOfIntersection(<IntersectionType>source, !!(target.flags & TypeFlags.Union));
if (constraint) {
if (result = isRelatedTo(constraint, target, reportErrors)) {
errorInfo = saveErrorInfo;
}
}
}
isIntersectionConstituent = saveIsIntersectionConstituent;
+3
View File
@@ -3620,6 +3620,9 @@ namespace ts {
BooleanLike = Boolean | BooleanLiteral,
EnumLike = Enum | EnumLiteral,
ESSymbolLike = ESSymbol | UniqueESSymbol,
VoidLike = Void | Undefined,
/* @internal */
DisjointDomains = NonPrimitive | StringLike | NumberLike | BooleanLike | ESSymbolLike | VoidLike | Null,
UnionOrIntersection = Union | Intersection,
StructuredType = Object | Union | Intersection,
TypeVariable = TypeParameter | IndexedAccess,