diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9ef54cd362..6b902c5ad89 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8468,137 +8468,38 @@ namespace ts { return result; } } - else if (target.flags & TypeFlags.Union) { - if (result = typeRelatedToSomeType(source, target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) { - return result; - } - } - else if (target.flags & TypeFlags.Intersection) { - if (result = typeRelatedToEachType(source, target as IntersectionType, reportErrors)) { - return result; - } - } - else if (source.flags & TypeFlags.Intersection) { - // 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. - if (result = someTypeRelatedToType(source, target, /*reportErrors*/ false)) { - return result; - } - } - else if (target.flags & TypeFlags.TypeParameter) { - // A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P]. - if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(source) === getIndexType(target)) { - if (!(source).declaration.questionToken) { - const templateType = getTemplateTypeFromMappedType(source); - const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source)); - if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) { - return result; - } - } - } - } - else if (target.flags & TypeFlags.Index) { - // A keyof S is related to a keyof T if T is related to S. - if (source.flags & TypeFlags.Index) { - if (result = isRelatedTo((target).type, (source).type, /*reportErrors*/ false)) { - return result; - } - } - // A type S is assignable to keyof T if S is assignable to keyof C, where C is the - // constraint of T. - const constraint = getConstraintOfType((target).type); - if (constraint) { - if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) { - return result; - } - } - } - else if (target.flags & TypeFlags.IndexedAccess) { - // A type S is related to a type T[K] if S is related to A[K], where K is string-like and - // A is the apparent type of S. - const constraint = getConstraintOfType(target); - if (constraint) { - if (result = isRelatedTo(source, constraint, reportErrors)) { - errorInfo = saveErrorInfo; - return result; - } - } - } - - if (source.flags & TypeFlags.TypeParameter) { - // A source type T is related to a target type { [P in keyof T]: X } if T[P] is related to X. - if (getObjectFlags(target) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(target) === getIndexType(source)) { - const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(target)); - const templateType = getTemplateTypeFromMappedType(target); - if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { - errorInfo = saveErrorInfo; - return result; - } - } - else { - let constraint = getConstraintOfTypeParameter(source); - // A type parameter with no constraint is not related to the non-primitive object type. - if (constraint || !(target.flags & TypeFlags.NonPrimitive)) { - if (!constraint || constraint.flags & TypeFlags.Any) { - constraint = emptyObjectType; - } - // The constraint may need to be further instantiated with its 'this' type. - constraint = getTypeWithThisArgument(constraint, source); - // Report constraint errors only if the constraint is not the empty object type - const reportConstraintErrors = reportErrors && constraint !== emptyObjectType; - if (result = isRelatedTo(constraint, target, reportConstraintErrors)) { - errorInfo = saveErrorInfo; - return result; - } - } - } - } - else if (source.flags & TypeFlags.IndexedAccess) { - // A type S[K] is related to a type T if A[K] is related to T, where K is string-like and - // A is the apparent type of S. - const constraint = getConstraintOfType(source); - if (constraint) { - if (result = isRelatedTo(constraint, target, reportErrors)) { - errorInfo = saveErrorInfo; - return result; - } - } - else if (target.flags & TypeFlags.IndexedAccess && (source).indexType === (target).indexType) { - // if we have indexed access types with identical index types, see if relationship holds for - // the two object types. - if (result = isRelatedTo((source).objectType, (target).objectType, reportErrors)) { - return result; - } - } - } else { - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { - // We have type references to same target type, see if relationship holds for all type arguments - if (result = typeArgumentsRelatedTo(source, target, reportErrors)) { + if (target.flags & TypeFlags.Union) { + if (result = typeRelatedToSomeType(source, target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) { return result; } } - // Even if relationship doesn't hold for unions, intersections, or generic type references, - // it may hold in a structural comparison. - const apparentSource = getApparentType(source); - // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates - // to X. Failing both of those we want to check if the aggregation of A and B's members structurally - // relates to X. Thus, we include intersection types on the source side here. - if (apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { - // Report structural errors only if we haven't reported any errors yet - const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !(source.flags & TypeFlags.Primitive); - if (result = objectTypeRelatedTo(apparentSource, source, target, reportStructuralErrors)) { + else if (target.flags & TypeFlags.Intersection) { + if (result = typeRelatedToEachType(source, target as IntersectionType, reportErrors)) { + return result; + } + } + else if (source.flags & TypeFlags.Intersection) { + // 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. + if (result = someTypeRelatedToType(source, target, /*reportErrors*/ false)) { + return result; + } + } + + if (source.flags & TypeFlags.StructuredOrTypeVariable || target.flags & TypeFlags.StructuredOrTypeVariable) { + if (result = recursiveTypeRelatedTo(source, target, reportErrors)) { errorInfo = saveErrorInfo; return result; } @@ -8620,13 +8521,7 @@ namespace ts { function isIdenticalTo(source: Type, target: Type): Ternary { let result: Ternary; if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { - // We have type references to same target type, see if all type arguments are identical - if (result = typeArgumentsRelatedTo(source, target, /*reportErrors*/ false)) { - return result; - } - } - return objectTypeRelatedTo(source, source, target, /*reportErrors*/ false); + return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false); } if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union || source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) { @@ -8815,12 +8710,12 @@ namespace ts { return result; } - // Determine if two object types are related by structure. First, check if the result is already available in the global cache. + // Determine if possibly recursive types are related. First, check if the result is already available in the global cache. // Second, check if we have already started a comparison of the given two types in which case we assume the result to be true. // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion // and issue an error. Otherwise, actually compare the structure of the two types. - function objectTypeRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary { + function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { if (overflow) { return Ternary.False; } @@ -8862,28 +8757,7 @@ namespace ts { const saveExpandingFlags = expandingFlags; if (!(expandingFlags & 1) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= 1; if (!(expandingFlags & 2) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= 2; - let result: Ternary; - if (expandingFlags === 3) { - result = Ternary.Maybe; - } - else if (isGenericMappedType(source) || isGenericMappedType(target)) { - result = mappedTypeRelatedTo(source, target, reportErrors); - } - else { - result = propertiesRelatedTo(source, target, reportErrors); - if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportErrors); - if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors); - if (result) { - result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.String, reportErrors); - if (result) { - result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.Number, reportErrors); - } - } - } - } - } + let result = expandingFlags !== 3 ? structuredTypeRelatedTo(source, target, reportErrors) : Ternary.Maybe; expandingFlags = saveExpandingFlags; depth--; if (result) { @@ -8900,6 +8774,141 @@ namespace ts { return result; } + function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { + let result: Ternary; + const saveErrorInfo = errorInfo; + if (target.flags & TypeFlags.TypeParameter) { + // A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P]. + if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(source) === getIndexType(target)) { + if (!(source).declaration.questionToken) { + const templateType = getTemplateTypeFromMappedType(source); + const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source)); + if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) { + return result; + } + } + } + } + else if (target.flags & TypeFlags.Index) { + // A keyof S is related to a keyof T if T is related to S. + if (source.flags & TypeFlags.Index) { + if (result = isRelatedTo((target).type, (source).type, /*reportErrors*/ false)) { + return result; + } + } + // A type S is assignable to keyof T if S is assignable to keyof C, where C is the + // constraint of T. + const constraint = getConstraintOfType((target).type); + if (constraint) { + if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) { + return result; + } + } + } + else if (target.flags & TypeFlags.IndexedAccess) { + // A type S is related to a type T[K] if S is related to A[K], where K is string-like and + // A is the apparent type of S. + const constraint = getConstraintOfType(target); + if (constraint) { + if (result = isRelatedTo(source, constraint, reportErrors)) { + errorInfo = saveErrorInfo; + return result; + } + } + } + + if (source.flags & TypeFlags.TypeParameter) { + // A source type T is related to a target type { [P in keyof T]: X } if T[P] is related to X. + if (getObjectFlags(target) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(target) === getIndexType(source)) { + const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(target)); + const templateType = getTemplateTypeFromMappedType(target); + if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { + errorInfo = saveErrorInfo; + return result; + } + } + else { + let constraint = getConstraintOfTypeParameter(source); + // A type parameter with no constraint is not related to the non-primitive object type. + if (constraint || !(target.flags & TypeFlags.NonPrimitive)) { + if (!constraint || constraint.flags & TypeFlags.Any) { + constraint = emptyObjectType; + } + // The constraint may need to be further instantiated with its 'this' type. + constraint = getTypeWithThisArgument(constraint, source); + // Report constraint errors only if the constraint is not the empty object type + const reportConstraintErrors = reportErrors && constraint !== emptyObjectType; + if (result = isRelatedTo(constraint, target, reportConstraintErrors)) { + errorInfo = saveErrorInfo; + return result; + } + } + } + } + else if (source.flags & TypeFlags.IndexedAccess) { + // A type S[K] is related to a type T if A[K] is related to T, where K is string-like and + // A is the apparent type of S. + const constraint = getConstraintOfType(source); + if (constraint) { + if (result = isRelatedTo(constraint, target, reportErrors)) { + errorInfo = saveErrorInfo; + return result; + } + } + else if (target.flags & TypeFlags.IndexedAccess && (source).indexType === (target).indexType) { + // if we have indexed access types with identical index types, see if relationship holds for + // the two object types. + if (result = isRelatedTo((source).objectType, (target).objectType, reportErrors)) { + return result; + } + } + } + else { + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { + // We have type references to same target type, see if relationship holds for all type arguments + if (result = typeArgumentsRelatedTo(source, target, reportErrors)) { + return result; + } + } + // Even if relationship doesn't hold for unions, intersections, or generic type references, + // it may hold in a structural comparison. + const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive); + if (relation !== identityRelation) { + source = getApparentType(source); + } + // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates + // to X. Failing both of those we want to check if the aggregation of A and B's members structurally + // relates to X. Thus, we include intersection types on the source side here. + if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { + // Report structural errors only if we haven't reported any errors yet + const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !sourceIsPrimitive; + if (isGenericMappedType(source) || isGenericMappedType(target)) { + result = mappedTypeRelatedTo(source, target, reportStructuralErrors); + } + else { + result = propertiesRelatedTo(source, target, reportStructuralErrors); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); + if (result) { + result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors); + if (result) { + result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors); + } + } + } + } + } + if (result) { + errorInfo = saveErrorInfo; + return result; + } + } + } + return Ternary.False; + } + // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice // that S and T are contra-variant whereas X and Y are co-variant. @@ -9148,12 +9157,12 @@ namespace ts { return related; } - function indexTypesRelatedTo(source: Type, originalSource: Type, target: Type, kind: IndexKind, reportErrors: boolean) { + function indexTypesRelatedTo(source: Type, target: Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean) { if (relation === identityRelation) { return indexTypesIdenticalTo(source, target, kind); } const targetInfo = getIndexInfoOfType(target, kind); - if (!targetInfo || ((targetInfo.type.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive))) { + if (!targetInfo || targetInfo.type.flags & TypeFlags.Any && !sourceIsPrimitive) { // Index signature of type any permits assignment from everything but primitives return Ternary.True; }