diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ddc3fd2074f..c67e2492782 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18342,47 +18342,43 @@ namespace ts { traceUnionsOrIntersectionsTooLarge(source, target); - let result = Ternary.False; - const saveErrorInfo = captureErrorCalculationState(); - 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); + let result = skipCaching ? + unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState) : + recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags); + // For certain combinations involving intersections and optional, excess, or mismatched properties we need + // an extra property check where the intersection is viewed as a single object. The following are motivating + // examples that all should be errors, but aren't without this extra property check: + // + // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property + // + // declare let wrong: { a: { y: string } }; + // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type + // + // function foo(x: { a?: string }, y: T & { a: boolean }) { + // x = y; // Mismatched property in source intersection + // } + // + // We suppress recursive intersection property checks because they can generate lots of work when relating + // recursive intersections that are structurally similar but not exactly identical. See #37854. + if (result && !inPropertyCheck && ( + target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) || + isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { + inPropertyCheck = true; + result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags); + inPropertyCheck = false; } - else if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) { - resetErrorInfo(saveErrorInfo); + if (result) { + return result; } } - // For certain combinations involving intersections and optional, excess, or mismatched properties we need - // an extra property check where the intersection is viewed as a single object. The following are motivating - // examples that all should be errors, but aren't without this extra property check: - // - // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property - // - // declare let wrong: { a: { y: string } }; - // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type - // - // function foo(x: { a?: string }, y: T & { a: boolean }) { - // x = y; // Mismatched property in source intersection - // } - // - // We suppress recursive intersection property checks because they can generate lots of work when relating - // recursive intersections that are structurally similar but not exactly identical. See #37854. - if (result && !inPropertyCheck && ( - target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) || - isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { - inPropertyCheck = true; - result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags); - inPropertyCheck = false; - } - - if (!result && reportErrors) { + if (reportErrors) { reportErrorResults(originalSource, originalTarget, source, target, headMessage); } - return result; + return Ternary.False; } function reportErrorResults(originalSource: Type, originalTarget: Type, source: Type, target: Type, headMessage: DiagnosticMessage | undefined) { @@ -19946,7 +19942,6 @@ namespace ts { } let result = Ternary.True; - const saveErrorInfo = captureErrorCalculationState(); const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn; const sourceObjectFlags = getObjectFlags(source); const targetObjectFlags = getObjectFlags(target); @@ -19985,6 +19980,7 @@ namespace ts { } else { outer: for (const t of targetSignatures) { + const saveErrorInfo = captureErrorCalculationState(); // Only elaborate errors from the first failure let shouldElaborateErrors = reportErrors; for (const s of sourceSignatures) { @@ -19996,7 +19992,6 @@ namespace ts { } shouldElaborateErrors = false; } - if (shouldElaborateErrors) { reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, typeToString(source),