mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-10 18:04:18 -05:00
Fix excess property checking for intersections with index signatures (#51894)
* Fix excess property checking for intersections with index signatures * Add regression tests * Limit check to only fresh object literals on the source side
This commit is contained in:
@@ -20708,6 +20708,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -21876,7 +21879,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return result;
|
||||
}
|
||||
|
||||
function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean): Ternary {
|
||||
function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
||||
let result = Ternary.True;
|
||||
const keyType = targetInfo.keyType;
|
||||
const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source as IntersectionType) : getPropertiesOfObjectType(source);
|
||||
@@ -21890,7 +21893,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const type = exactOptionalPropertyTypes || propType.flags & TypeFlags.Undefined || keyType === numberType || !(prop.flags & SymbolFlags.Optional)
|
||||
? propType
|
||||
: getTypeWithFacts(propType, TypeFacts.NEUndefined);
|
||||
const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors);
|
||||
const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
|
||||
if (!related) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop));
|
||||
@@ -21951,7 +21954,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) {
|
||||
// Intersection constituents are never considered to have an inferred index signature
|
||||
return membersRelatedToIndexInfo(source, targetInfo, reportErrors);
|
||||
return membersRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState);
|
||||
}
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source));
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts(5,7): error TS2322: Type '{ a: 0; }' is not assignable to type '{ a: 0; } & { b: 0; }'.
|
||||
Property 'b' is missing in type '{ a: 0; }' but required in type '{ b: 0; }'.
|
||||
tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts(7,24): error TS2322: Type '{ a: 0; b: 0; c: number; }' is not assignable to type '{ a: 0; } & { b: 0; }'.
|
||||
Object literal may only specify known properties, and 'c' does not exist in type '{ a: 0; } & { b: 0; }'.
|
||||
|
||||
|
||||
==== tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts (2 errors) ====
|
||||
// Repro from #51875
|
||||
|
||||
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
|
||||
|
||||
x = { y: { a: 0 } }; // Error
|
||||
~
|
||||
!!! error TS2322: Type '{ a: 0; }' is not assignable to type '{ a: 0; } & { b: 0; }'.
|
||||
!!! error TS2322: Property 'b' is missing in type '{ a: 0; }' but required in type '{ b: 0; }'.
|
||||
!!! related TS2728 tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts:3:53: 'b' is declared here.
|
||||
x = { y: { a: 0, b: 0 } };
|
||||
x = { y: { a: 0, b: 0, c: 0 } }; // Error
|
||||
~~~~
|
||||
!!! error TS2322: Type '{ a: 0; b: 0; c: number; }' is not assignable to type '{ a: 0; } & { b: 0; }'.
|
||||
!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type '{ a: 0; } & { b: 0; }'.
|
||||
|
||||
type A = { a: string };
|
||||
type B = { b: string };
|
||||
|
||||
const yy: Record<string, A> & Record<string, B> = {
|
||||
foo: { a: '', b: '' },
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
//// [excessPropertyCheckIntersectionWithIndexSignature.ts]
|
||||
// Repro from #51875
|
||||
|
||||
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
|
||||
|
||||
x = { y: { a: 0 } }; // Error
|
||||
x = { y: { a: 0, b: 0 } };
|
||||
x = { y: { a: 0, b: 0, c: 0 } }; // Error
|
||||
|
||||
type A = { a: string };
|
||||
type B = { b: string };
|
||||
|
||||
const yy: Record<string, A> & Record<string, B> = {
|
||||
foo: { a: '', b: '' },
|
||||
};
|
||||
|
||||
|
||||
//// [excessPropertyCheckIntersectionWithIndexSignature.js]
|
||||
"use strict";
|
||||
// Repro from #51875
|
||||
var x;
|
||||
x = { y: { a: 0 } }; // Error
|
||||
x = { y: { a: 0, b: 0 } };
|
||||
x = { y: { a: 0, b: 0, c: 0 } }; // Error
|
||||
var yy = {
|
||||
foo: { a: '', b: '' },
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
=== tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts ===
|
||||
// Repro from #51875
|
||||
|
||||
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
|
||||
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 3))
|
||||
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 10))
|
||||
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 23))
|
||||
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 38))
|
||||
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 51))
|
||||
|
||||
x = { y: { a: 0 } }; // Error
|
||||
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 3))
|
||||
>y : Symbol(y, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 4, 5))
|
||||
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 4, 10))
|
||||
|
||||
x = { y: { a: 0, b: 0 } };
|
||||
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 3))
|
||||
>y : Symbol(y, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 5, 5))
|
||||
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 5, 10))
|
||||
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 5, 16))
|
||||
|
||||
x = { y: { a: 0, b: 0, c: 0 } }; // Error
|
||||
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 3))
|
||||
>y : Symbol(y, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 5))
|
||||
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 10))
|
||||
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 16))
|
||||
>c : Symbol(c, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 22))
|
||||
|
||||
type A = { a: string };
|
||||
>A : Symbol(A, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 32))
|
||||
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 8, 10))
|
||||
|
||||
type B = { b: string };
|
||||
>B : Symbol(B, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 8, 23))
|
||||
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 9, 10))
|
||||
|
||||
const yy: Record<string, A> & Record<string, B> = {
|
||||
>yy : Symbol(yy, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 11, 5))
|
||||
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
|
||||
>A : Symbol(A, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 32))
|
||||
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
|
||||
>B : Symbol(B, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 8, 23))
|
||||
|
||||
foo: { a: '', b: '' },
|
||||
>foo : Symbol(foo, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 11, 51))
|
||||
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 12, 10))
|
||||
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 12, 17))
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
=== tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts ===
|
||||
// Repro from #51875
|
||||
|
||||
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
|
||||
>x : { [x: string]: { a: 0; }; } & { [x: string]: { b: 0; }; }
|
||||
>x : string
|
||||
>a : 0
|
||||
>x : string
|
||||
>b : 0
|
||||
|
||||
x = { y: { a: 0 } }; // Error
|
||||
>x = { y: { a: 0 } } : { y: { a: 0; }; }
|
||||
>x : { [x: string]: { a: 0; }; } & { [x: string]: { b: 0; }; }
|
||||
>{ y: { a: 0 } } : { y: { a: 0; }; }
|
||||
>y : { a: 0; }
|
||||
>{ a: 0 } : { a: 0; }
|
||||
>a : 0
|
||||
>0 : 0
|
||||
|
||||
x = { y: { a: 0, b: 0 } };
|
||||
>x = { y: { a: 0, b: 0 } } : { y: { a: 0; b: 0; }; }
|
||||
>x : { [x: string]: { a: 0; }; } & { [x: string]: { b: 0; }; }
|
||||
>{ y: { a: 0, b: 0 } } : { y: { a: 0; b: 0; }; }
|
||||
>y : { a: 0; b: 0; }
|
||||
>{ a: 0, b: 0 } : { a: 0; b: 0; }
|
||||
>a : 0
|
||||
>0 : 0
|
||||
>b : 0
|
||||
>0 : 0
|
||||
|
||||
x = { y: { a: 0, b: 0, c: 0 } }; // Error
|
||||
>x = { y: { a: 0, b: 0, c: 0 } } : { y: { a: 0; b: 0; c: number; }; }
|
||||
>x : { [x: string]: { a: 0; }; } & { [x: string]: { b: 0; }; }
|
||||
>{ y: { a: 0, b: 0, c: 0 } } : { y: { a: 0; b: 0; c: number; }; }
|
||||
>y : { a: 0; b: 0; c: number; }
|
||||
>{ a: 0, b: 0, c: 0 } : { a: 0; b: 0; c: number; }
|
||||
>a : 0
|
||||
>0 : 0
|
||||
>b : 0
|
||||
>0 : 0
|
||||
>c : number
|
||||
>0 : 0
|
||||
|
||||
type A = { a: string };
|
||||
>A : { a: string; }
|
||||
>a : string
|
||||
|
||||
type B = { b: string };
|
||||
>B : { b: string; }
|
||||
>b : string
|
||||
|
||||
const yy: Record<string, A> & Record<string, B> = {
|
||||
>yy : Record<string, A> & Record<string, B>
|
||||
>{ foo: { a: '', b: '' },} : { foo: { a: string; b: string; }; }
|
||||
|
||||
foo: { a: '', b: '' },
|
||||
>foo : { a: string; b: string; }
|
||||
>{ a: '', b: '' } : { a: string; b: string; }
|
||||
>a : string
|
||||
>'' : ""
|
||||
>b : string
|
||||
>'' : ""
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// @strict: true
|
||||
|
||||
// Repro from #51875
|
||||
|
||||
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
|
||||
|
||||
x = { y: { a: 0 } }; // Error
|
||||
x = { y: { a: 0, b: 0 } };
|
||||
x = { y: { a: 0, b: 0, c: 0 } }; // Error
|
||||
|
||||
type A = { a: string };
|
||||
type B = { b: string };
|
||||
|
||||
const yy: Record<string, A> & Record<string, B> = {
|
||||
foo: { a: '', b: '' },
|
||||
};
|
||||
Reference in New Issue
Block a user