diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 05b8411b696ad..6370cd13625f3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1261,8 +1261,8 @@ export const enum SignatureCheckMode { const enum IntersectionState { None = 0, - Source = 1 << 0, - Target = 1 << 1, + Source = 1 << 0, // Source type is a constituent of an outer intersection + Target = 1 << 1, // Target type is a constituent of an outer intersection } const enum RecursionFlags { @@ -19990,7 +19990,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid let lastSkippedInfo: [Type, Type] | undefined; let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] | undefined; - let inPropertyCheck = false; Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); @@ -20955,30 +20954,33 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); } } - // 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: + // When the target is an intersection we need an extra property check in order to detect nested excess + // properties and nested weak types. 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 // + if (result && !(intersectionState & IntersectionState.Target) && target.flags & TypeFlags.Intersection && + !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { + result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, IntersectionState.None); + if (result && isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) { + result &= indexSignaturesRelatedTo(source, target, /*sourceIsPrimitive*/ false, reportErrors, IntersectionState.None); + } + } + // When the source is an intersection we need an extra check of any optional properties in the target to + // detect possible mismatched property types. For example: + // // 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 && !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection) || - isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { - inPropertyCheck = true; - result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None); - if (result && isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) { - result &= indexSignaturesRelatedTo(source, target, /*sourceIsPrimitive*/ false, reportErrors, IntersectionState.None); - } - inPropertyCheck = false; + else if (result && isNonGenericObjectType(target) && !isArrayOrTupleType(target) && + source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && + !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType))) { + result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ true, intersectionState); } } if (result) { @@ -21486,11 +21488,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (sourceFlags & (TypeFlags.Object | TypeFlags.Intersection) && targetFlags & TypeFlags.Object) { // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; - result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState); + result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, intersectionState); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors, intersectionState); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors, intersectionState); if (result) { result &= indexSignaturesRelatedTo(source, target, sourceIsPrimitive, reportStructuralErrors, intersectionState); } @@ -21661,11 +21663,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Compare the remaining non-discriminant properties of each match. let result = Ternary.True; for (const type of matchingTypes) { - result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None); + result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, /*optionalsOnly*/ false, IntersectionState.None); if (result) { - result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); + result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false, IntersectionState.None); if (result) { - result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); + result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false, IntersectionState.None); if (result && !(isTupleType(source) && isTupleType(type))) { // Comparing numeric index types when both `source` and `type` are tuples is unnecessary as the // element types should be sufficiently covered by `propertiesRelatedTo`. It also causes problems @@ -21828,7 +21830,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // No array like or unmatched property error - just issue top level error (errorInfo = undefined) } - function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, intersectionState: IntersectionState): Ternary { + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, optionalsOnly: boolean, intersectionState: IntersectionState): Ternary { if (relation === identityRelation) { return propertiesIdenticalTo(source, target, excludedProperties); } @@ -21963,7 +21965,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const numericNamesOnly = isTupleType(source) && isTupleType(target); for (const targetProp of excludeProperties(properties, excludedProperties)) { const name = targetProp.escapedName; - if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) { + if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length") && (!optionalsOnly || targetProp.flags & SymbolFlags.Optional)) { const sourceProp = getPropertyOfType(source, name); if (sourceProp && sourceProp !== targetProp) { const related = propertyRelatedTo(source, target, sourceProp, targetProp, getNonMissingTypeOfSymbol, reportErrors, intersectionState, relation === comparableRelation); @@ -22001,7 +22003,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean): Ternary { + function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (relation === identityRelation) { return signaturesIdenticalTo(source, target, kind); } @@ -22046,7 +22048,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // of the much more expensive N * M comparison matrix we explore below. We erase type parameters // as they are known to always be the same. for (let i = 0; i < targetSignatures.length; i++) { - const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); + const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, intersectionState, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); if (!related) { return Ternary.False; } @@ -22062,7 +22064,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks; const sourceSignature = first(sourceSignatures); const targetSignature = first(targetSignatures); - result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, incompatibleReporter(sourceSignature, targetSignature)); + result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, intersectionState, incompatibleReporter(sourceSignature, targetSignature)); if (!result && reportErrors && kind === SignatureKind.Construct && (sourceObjectFlags & targetObjectFlags) && (targetSignature.declaration?.kind === SyntaxKind.Constructor || sourceSignature.declaration?.kind === SyntaxKind.Constructor)) { const constructSignatureToString = (signature: Signature) => @@ -22078,7 +22080,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Only elaborate errors from the first failure let shouldElaborateErrors = reportErrors; for (const s of sourceSignatures) { - const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t)); + const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, intersectionState, incompatibleReporter(s, t)); if (related) { result &= related; resetErrorInfo(saveErrorInfo); @@ -22128,9 +22130,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { /** * See signatureAssignableTo, compareSignaturesIdentical */ - function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { + function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, intersectionState: IntersectionState, incompatibleReporter: (source: Type, target: Type) => void): Ternary { return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); + function isRelatedToWorker(source: Type, target: Type, reportErrors?: boolean) { + return isRelatedTo(source, target, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } } function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { @@ -22176,7 +22181,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } for (const info of getIndexInfosOfType(source)) { if (isApplicableIndexType(info.keyType, keyType)) { - const related = indexInfoRelatedTo(info, targetInfo, reportErrors); + const related = indexInfoRelatedTo(info, targetInfo, reportErrors, intersectionState); if (!related) { return Ternary.False; } @@ -22186,8 +22191,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) { - const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors); + function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState) { + const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related && reportErrors) { if (sourceInfo.keyType === targetInfo.keyType) { reportError(Diagnostics._0_index_signatures_are_incompatible, typeToString(sourceInfo.keyType)); @@ -22221,7 +22226,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function typeRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); if (sourceInfo) { - return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); + return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors, intersectionState); } if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) { // Intersection constituents are never considered to have an inferred index signature @@ -22597,10 +22602,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons - // for maxDepth or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, - // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely - // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth - // levels, but unequal at some level beyond that. + // for maxDepth or more occurrences or instantiations of the same type have been recorded on the given stack. The + // "sameness" of instantiations is determined by the getRecursionIdentity function. An intersection is considered + // deeply nested if any constituent of the intersection is deeply nested. It is possible, though highly unlikely, for + // the deeply nested check to be true in a situation where a chain of instantiations is not infinitely expanding. + // Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth levels, + // but unequal at some level beyond that. // In addition, this will also detect when an indexed access has been chained off of maxDepth more times (which is // essentially the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding // false positives for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). @@ -22610,12 +22617,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // to terminate the expansion, and we do so here. function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { if (depth >= maxDepth) { + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, t => isDeeplyNestedType(t, stack, depth, maxDepth)); + } const identity = getRecursionIdentity(type); let count = 0; let lastTypeId = 0; for (let i = 0; i < depth; i++) { const t = stack[i]; - if (getRecursionIdentity(t) === identity) { + if (t.flags & TypeFlags.Intersection ? some((t as IntersectionType).types, u => getRecursionIdentity(u) === identity) : getRecursionIdentity(t) === identity) { // We only count occurrences with a higher type id than the previous occurrence, since higher // type ids are an indicator of newer instantiations caused by recursion. if (t.id >= lastTypeId) { diff --git a/tests/baselines/reference/nestedExcessPropertyChecking.errors.txt b/tests/baselines/reference/nestedExcessPropertyChecking.errors.txt index c7704691e1e29..ca3b22560a67d 100644 --- a/tests/baselines/reference/nestedExcessPropertyChecking.errors.txt +++ b/tests/baselines/reference/nestedExcessPropertyChecking.errors.txt @@ -67,4 +67,25 @@ tests/cases/compiler/nestedExcessPropertyChecking.ts(40,9): error TS2559: Type ' !!! related TS6500 tests/cases/compiler/nestedExcessPropertyChecking.ts:35:24: The expected type comes from property 'overrides' which is declared here on type 'VariablesA & VariablesB' } }; + + // Simplified repro from #52252 + + type T1 = { + primary: { __typename?: 'Feature' } & { colors: { light: number, dark: number } }, + }; + + type T2 = { + primary: { __typename?: 'Feature' } & { colors: { light: number } }, + }; + + type Query = T1 & T2; + + const response: Query = { + primary: { + colors: { + light: 1, + dark: 3, + }, + }, + }; \ No newline at end of file diff --git a/tests/baselines/reference/nestedExcessPropertyChecking.js b/tests/baselines/reference/nestedExcessPropertyChecking.js index 0fe6a42c1d292..d88f92eca98fc 100644 --- a/tests/baselines/reference/nestedExcessPropertyChecking.js +++ b/tests/baselines/reference/nestedExcessPropertyChecking.js @@ -41,6 +41,27 @@ const foo2: Unrelated & { variables: VariablesA & VariablesB } = { overrides: false // Error } }; + +// Simplified repro from #52252 + +type T1 = { + primary: { __typename?: 'Feature' } & { colors: { light: number, dark: number } }, +}; + +type T2 = { + primary: { __typename?: 'Feature' } & { colors: { light: number } }, +}; + +type Query = T1 & T2; + +const response: Query = { + primary: { + colors: { + light: 1, + dark: 3, + }, + }, +}; //// [nestedExcessPropertyChecking.js] @@ -59,3 +80,11 @@ var foo2 = { overrides: false // Error } }; +var response = { + primary: { + colors: { + light: 1, + dark: 3, + }, + }, +}; diff --git a/tests/baselines/reference/nestedExcessPropertyChecking.symbols b/tests/baselines/reference/nestedExcessPropertyChecking.symbols index 2a17170e90a2d..137b2eda98868 100644 --- a/tests/baselines/reference/nestedExcessPropertyChecking.symbols +++ b/tests/baselines/reference/nestedExcessPropertyChecking.symbols @@ -113,3 +113,53 @@ const foo2: Unrelated & { variables: VariablesA & VariablesB } = { } }; +// Simplified repro from #52252 + +type T1 = { +>T1 : Symbol(T1, Decl(nestedExcessPropertyChecking.ts, 41, 2)) + + primary: { __typename?: 'Feature' } & { colors: { light: number, dark: number } }, +>primary : Symbol(primary, Decl(nestedExcessPropertyChecking.ts, 45, 11)) +>__typename : Symbol(__typename, Decl(nestedExcessPropertyChecking.ts, 46, 14)) +>colors : Symbol(colors, Decl(nestedExcessPropertyChecking.ts, 46, 43)) +>light : Symbol(light, Decl(nestedExcessPropertyChecking.ts, 46, 53)) +>dark : Symbol(dark, Decl(nestedExcessPropertyChecking.ts, 46, 68)) + +}; + +type T2 = { +>T2 : Symbol(T2, Decl(nestedExcessPropertyChecking.ts, 47, 2)) + + primary: { __typename?: 'Feature' } & { colors: { light: number } }, +>primary : Symbol(primary, Decl(nestedExcessPropertyChecking.ts, 49, 11)) +>__typename : Symbol(__typename, Decl(nestedExcessPropertyChecking.ts, 50, 14)) +>colors : Symbol(colors, Decl(nestedExcessPropertyChecking.ts, 50, 43)) +>light : Symbol(light, Decl(nestedExcessPropertyChecking.ts, 50, 53)) + +}; + +type Query = T1 & T2; +>Query : Symbol(Query, Decl(nestedExcessPropertyChecking.ts, 51, 2)) +>T1 : Symbol(T1, Decl(nestedExcessPropertyChecking.ts, 41, 2)) +>T2 : Symbol(T2, Decl(nestedExcessPropertyChecking.ts, 47, 2)) + +const response: Query = { +>response : Symbol(response, Decl(nestedExcessPropertyChecking.ts, 55, 5)) +>Query : Symbol(Query, Decl(nestedExcessPropertyChecking.ts, 51, 2)) + + primary: { +>primary : Symbol(primary, Decl(nestedExcessPropertyChecking.ts, 55, 25)) + + colors: { +>colors : Symbol(colors, Decl(nestedExcessPropertyChecking.ts, 56, 14)) + + light: 1, +>light : Symbol(light, Decl(nestedExcessPropertyChecking.ts, 57, 17)) + + dark: 3, +>dark : Symbol(dark, Decl(nestedExcessPropertyChecking.ts, 58, 21)) + + }, + }, +}; + diff --git a/tests/baselines/reference/nestedExcessPropertyChecking.types b/tests/baselines/reference/nestedExcessPropertyChecking.types index 473b1f5295525..9672f50674bd5 100644 --- a/tests/baselines/reference/nestedExcessPropertyChecking.types +++ b/tests/baselines/reference/nestedExcessPropertyChecking.types @@ -106,3 +106,55 @@ const foo2: Unrelated & { variables: VariablesA & VariablesB } = { } }; +// Simplified repro from #52252 + +type T1 = { +>T1 : { primary: { __typename?: 'Feature';} & { colors: { light: number; dark: number; };}; } + + primary: { __typename?: 'Feature' } & { colors: { light: number, dark: number } }, +>primary : { __typename?: "Feature" | undefined; } & { colors: { light: number; dark: number;}; } +>__typename : "Feature" | undefined +>colors : { light: number; dark: number; } +>light : number +>dark : number + +}; + +type T2 = { +>T2 : { primary: { __typename?: 'Feature';} & { colors: { light: number; };}; } + + primary: { __typename?: 'Feature' } & { colors: { light: number } }, +>primary : { __typename?: "Feature" | undefined; } & { colors: { light: number;}; } +>__typename : "Feature" | undefined +>colors : { light: number; } +>light : number + +}; + +type Query = T1 & T2; +>Query : T1 & T2 + +const response: Query = { +>response : Query +>{ primary: { colors: { light: 1, dark: 3, }, },} : { primary: { colors: { light: number; dark: number; }; }; } + + primary: { +>primary : { colors: { light: number; dark: number; }; } +>{ colors: { light: 1, dark: 3, }, } : { colors: { light: number; dark: number; }; } + + colors: { +>colors : { light: number; dark: number; } +>{ light: 1, dark: 3, } : { light: number; dark: number; } + + light: 1, +>light : number +>1 : 1 + + dark: 3, +>dark : number +>3 : 3 + + }, + }, +}; + diff --git a/tests/baselines/reference/unionThisTypeInFunctions.errors.txt b/tests/baselines/reference/unionThisTypeInFunctions.errors.txt index f4f0377a0874c..db03a5426fb43 100644 --- a/tests/baselines/reference/unionThisTypeInFunctions.errors.txt +++ b/tests/baselines/reference/unionThisTypeInFunctions.errors.txt @@ -5,12 +5,8 @@ tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts(10,5): error Type '(this: Real, n: number) => void' is not assignable to type '(this: Fake, n: number) => void'. The 'this' types of each signature are incompatible. Type 'Fake' is not assignable to type 'Real'. - Types of property 'method' are incompatible. - Type '(this: Fake, n: number) => void' is not assignable to type '(this: Real, n: number) => void'. - The 'this' types of each signature are incompatible. - Type 'Real' is not assignable to type 'Fake'. - Types of property 'data' are incompatible. - Type 'string' is not assignable to type 'number'. + Types of property 'data' are incompatible. + Type 'number' is not assignable to type 'string'. ==== tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts (1 errors) ==== @@ -32,11 +28,7 @@ tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts(10,5): error !!! error TS2684: Type '(this: Real, n: number) => void' is not assignable to type '(this: Fake, n: number) => void'. !!! error TS2684: The 'this' types of each signature are incompatible. !!! error TS2684: Type 'Fake' is not assignable to type 'Real'. -!!! error TS2684: Types of property 'method' are incompatible. -!!! error TS2684: Type '(this: Fake, n: number) => void' is not assignable to type '(this: Real, n: number) => void'. -!!! error TS2684: The 'this' types of each signature are incompatible. -!!! error TS2684: Type 'Real' is not assignable to type 'Fake'. -!!! error TS2684: Types of property 'data' are incompatible. -!!! error TS2684: Type 'string' is not assignable to type 'number'. +!!! error TS2684: Types of property 'data' are incompatible. +!!! error TS2684: Type 'number' is not assignable to type 'string'. } \ No newline at end of file diff --git a/tests/cases/compiler/nestedExcessPropertyChecking.ts b/tests/cases/compiler/nestedExcessPropertyChecking.ts index 151a27c0962df..9e7c808b5e0ec 100644 --- a/tests/cases/compiler/nestedExcessPropertyChecking.ts +++ b/tests/cases/compiler/nestedExcessPropertyChecking.ts @@ -42,3 +42,24 @@ const foo2: Unrelated & { variables: VariablesA & VariablesB } = { overrides: false // Error } }; + +// Simplified repro from #52252 + +type T1 = { + primary: { __typename?: 'Feature' } & { colors: { light: number, dark: number } }, +}; + +type T2 = { + primary: { __typename?: 'Feature' } & { colors: { light: number } }, +}; + +type Query = T1 & T2; + +const response: Query = { + primary: { + colors: { + light: 1, + dark: 3, + }, + }, +};