diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ff2c8590d0..b65b1ed3438 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4668,19 +4668,21 @@ namespace ts { let result: Ternary; // both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases if (source === target) return Ternary.True; - if (relation !== identityRelation) { - if (isTypeAny(target)) return Ternary.True; - if (source === undefinedType) return Ternary.True; - if (source === nullType && target !== undefinedType) return Ternary.True; - if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True; - if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True; - if (relation === assignableRelation) { - if (isTypeAny(source)) return Ternary.True; - if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True; - } + if (relation === identityRelation) { + return isIdenticalTo(source, target); } - if (relation !== identityRelation && source.flags & TypeFlags.FreshObjectLiteral) { + if (isTypeAny(target)) return Ternary.True; + if (source === undefinedType) return Ternary.True; + if (source === nullType && target !== undefinedType) return Ternary.True; + if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True; + if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True; + if (relation === assignableRelation) { + if (isTypeAny(source)) return Ternary.True; + if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True; + } + + if (source.flags & TypeFlags.FreshObjectLiteral) { if (hasExcessProperties(source, target, reportErrors)) { if (reportErrors) { reportRelationError(headMessage, source, target); @@ -4696,78 +4698,66 @@ namespace ts { let saveErrorInfo = errorInfo; - if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (source).target === (target).target) { - // We have type references to same target type, see if relationship holds for all type arguments - if (result = typesRelatedTo((source).typeArguments, (target).typeArguments, reportErrors)) { + // Note that the "each" checks must precede the "some" checks to produce the correct results + if (source.flags & TypeFlags.Union) { + if (result = eachTypeRelatedToType(source, target, reportErrors)) { return result; } } - else if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) { - if (result = typeParameterRelatedTo(source, target, reportErrors)) { + else if (target.flags & TypeFlags.Intersection) { + if (result = typeRelatedToEachType(source, target, reportErrors)) { return result; } } - else if (relation !== identityRelation) { - // Note that the "each" checks must precede the "some" checks to produce the correct results - if (source.flags & TypeFlags.Union) { - if (result = eachTypeRelatedToType(source, target, reportErrors)) { - return result; - } - } - else if (target.flags & TypeFlags.Intersection) { - if (result = typeRelatedToEachType(source, target, reportErrors)) { - return result; - } - } - else { - // It is necessary to try "each" checks on both sides because there may be nested "some" checks - // on either side that need to be prioritized. For example, A | B = (A | B) & (C | D) or - // A & B = (A & B) | (C & D). - if (source.flags & TypeFlags.Intersection) { - // If target is a union type the following check will report errors so we suppress them here - if (result = someTypeRelatedToType(source, target, reportErrors && !(target.flags & TypeFlags.Union))) { - return result; - } - } - if (target.flags & TypeFlags.Union) { - if (result = typeRelatedToSomeType(source, target, reportErrors)) { - return result; - } - } - } - } else { - if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union || - source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) { - if (result = eachTypeRelatedToSomeType(source, target)) { - if (result &= eachTypeRelatedToSomeType(target, source)) { - return result; - } + // It is necessary to try "each" checks on both sides because there may be nested "some" checks + // on either side that need to be prioritized. For example, A | B = (A | B) & (C | D) or + // A & B = (A & B) | (C & D). + if (source.flags & TypeFlags.Intersection) { + // If target is a union type the following check will report errors so we suppress them here + if (result = someTypeRelatedToType(source, target, reportErrors && !(target.flags & TypeFlags.Union))) { + return result; + } + } + if (target.flags & TypeFlags.Union) { + if (result = typeRelatedToSomeType(source, target, reportErrors)) { + return result; } } } - // Even if relationship doesn't hold for unions, type parameters, or generic type references, - // it may hold in a structural comparison. - // Report structural errors only if we haven't reported any errors yet - let reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo; - // Identity relation does not use apparent type - let sourceOrApparentType = 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 (sourceOrApparentType.flags & (TypeFlags.ObjectType | TypeFlags.Intersection) && target.flags & TypeFlags.ObjectType) { - if (result = objectTypeRelatedTo(sourceOrApparentType, target, reportStructuralErrors)) { + if (source.flags & TypeFlags.TypeParameter) { + let constraint = getConstraintOfTypeParameter(source); + if (!constraint || constraint.flags & TypeFlags.Any) { + constraint = emptyObjectType; + } + // Report constraint errors only if the constraint is not the empty object type + let reportConstraintErrors = reportErrors && constraint !== emptyObjectType; + if (result = isRelatedTo(constraint, target, reportConstraintErrors)) { errorInfo = saveErrorInfo; return result; } } - else if (source.flags & TypeFlags.TypeParameter && sourceOrApparentType.flags & TypeFlags.UnionOrIntersection) { - // We clear the errors first because the following check often gives a better error than - // the union or intersection comparison above if it is applicable. - errorInfo = saveErrorInfo; - if (result = isRelatedTo(sourceOrApparentType, target, reportErrors)) { - return result; + else { + if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (source).target === (target).target) { + // We have type references to same target type, see if relationship holds for all type arguments + if (result = typesRelatedTo((source).typeArguments, (target).typeArguments, reportErrors)) { + return result; + } + } + // Even if relationship doesn't hold for unions, intersections, or generic type references, + // it may hold in a structural comparison. + let apparentType = 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 (apparentType.flags & (TypeFlags.ObjectType | TypeFlags.Intersection) && target.flags & TypeFlags.ObjectType) { + // Report structural errors only if we haven't reported any errors yet + let reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo; + if (result = objectTypeRelatedTo(apparentType, target, reportStructuralErrors)) { + errorInfo = saveErrorInfo; + return result; + } } } @@ -4777,6 +4767,31 @@ namespace ts { return Ternary.False; } + function isIdenticalTo(source: Type, target: Type): Ternary { + let result: Ternary; + if (source.flags & TypeFlags.ObjectType && target.flags & TypeFlags.ObjectType) { + if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (source).target === (target).target) { + // We have type references to same target type, see if all type arguments are identical + if (result = typesRelatedTo((source).typeArguments, (target).typeArguments, /*reportErrors*/ false)) { + return result; + } + } + return objectTypeRelatedTo(source, target, /*reportErrors*/ false); + } + if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) { + return typeParameterIdenticalTo(source, target); + } + if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union || + source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) { + if (result = eachTypeRelatedToSomeType(source, target)) { + if (result &= eachTypeRelatedToSomeType(target, source)) { + return result; + } + } + } + return Ternary.False; + } + function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { for (let prop of getPropertiesOfObjectType(source)) { if (!isKnownProperty(target, prop.name)) { @@ -4861,29 +4876,18 @@ namespace ts { return result; } - function typeParameterRelatedTo(source: TypeParameter, target: TypeParameter, reportErrors: boolean): Ternary { - if (relation === identityRelation) { - if (source.symbol.name !== target.symbol.name) { - return Ternary.False; - } - // covers case when both type parameters does not have constraint (both equal to noConstraintType) - if (source.constraint === target.constraint) { - return Ternary.True; - } - if (source.constraint === noConstraintType || target.constraint === noConstraintType) { - return Ternary.False; - } - return isRelatedTo(source.constraint, target.constraint, reportErrors); - } - else { - while (true) { - let constraint = getConstraintOfTypeParameter(source); - if (constraint === target) return Ternary.True; - if (!(constraint && constraint.flags & TypeFlags.TypeParameter)) break; - source = constraint; - } + function typeParameterIdenticalTo(source: TypeParameter, target: TypeParameter): Ternary { + if (source.symbol.name !== target.symbol.name) { return Ternary.False; } + // covers case when both type parameters does not have constraint (both equal to noConstraintType) + if (source.constraint === target.constraint) { + return Ternary.True; + } + if (source.constraint === noConstraintType || target.constraint === noConstraintType) { + return Ternary.False; + } + return isRelatedTo(source.constraint, target.constraint, /*reportErrors*/ false); } // Determine if two object types are related by structure. First, check if the result is already available in the global cache.