Skip to content

Commit ba793e6

Browse files
authored
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
1 parent ff919e3 commit ba793e6

6 files changed

+193
-3
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20708,6 +20708,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2070820708
isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) {
2070920709
inPropertyCheck = true;
2071020710
result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None);
20711+
if (result && isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) {
20712+
result &= indexSignaturesRelatedTo(source, target, /*sourceIsPrimitive*/ false, reportErrors, IntersectionState.None);
20713+
}
2071120714
inPropertyCheck = false;
2071220715
}
2071320716
}
@@ -21876,7 +21879,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2187621879
return result;
2187721880
}
2187821881

21879-
function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean): Ternary {
21882+
function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
2188021883
let result = Ternary.True;
2188121884
const keyType = targetInfo.keyType;
2188221885
const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source as IntersectionType) : getPropertiesOfObjectType(source);
@@ -21890,7 +21893,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2189021893
const type = exactOptionalPropertyTypes || propType.flags & TypeFlags.Undefined || keyType === numberType || !(prop.flags & SymbolFlags.Optional)
2189121894
? propType
2189221895
: getTypeWithFacts(propType, TypeFacts.NEUndefined);
21893-
const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors);
21896+
const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
2189421897
if (!related) {
2189521898
if (reportErrors) {
2189621899
reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop));
@@ -21951,7 +21954,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2195121954
}
2195221955
if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) {
2195321956
// Intersection constituents are never considered to have an inferred index signature
21954-
return membersRelatedToIndexInfo(source, targetInfo, reportErrors);
21957+
return membersRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState);
2195521958
}
2195621959
if (reportErrors) {
2195721960
reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source));
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts(5,7): error TS2322: Type '{ a: 0; }' is not assignable to type '{ a: 0; } & { b: 0; }'.
2+
Property 'b' is missing in type '{ a: 0; }' but required in type '{ b: 0; }'.
3+
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; }'.
4+
Object literal may only specify known properties, and 'c' does not exist in type '{ a: 0; } & { b: 0; }'.
5+
6+
7+
==== tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts (2 errors) ====
8+
// Repro from #51875
9+
10+
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
11+
12+
x = { y: { a: 0 } }; // Error
13+
~
14+
!!! error TS2322: Type '{ a: 0; }' is not assignable to type '{ a: 0; } & { b: 0; }'.
15+
!!! error TS2322: Property 'b' is missing in type '{ a: 0; }' but required in type '{ b: 0; }'.
16+
!!! related TS2728 tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts:3:53: 'b' is declared here.
17+
x = { y: { a: 0, b: 0 } };
18+
x = { y: { a: 0, b: 0, c: 0 } }; // Error
19+
~~~~
20+
!!! error TS2322: Type '{ a: 0; b: 0; c: number; }' is not assignable to type '{ a: 0; } & { b: 0; }'.
21+
!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type '{ a: 0; } & { b: 0; }'.
22+
23+
type A = { a: string };
24+
type B = { b: string };
25+
26+
const yy: Record<string, A> & Record<string, B> = {
27+
foo: { a: '', b: '' },
28+
};
29+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [excessPropertyCheckIntersectionWithIndexSignature.ts]
2+
// Repro from #51875
3+
4+
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
5+
6+
x = { y: { a: 0 } }; // Error
7+
x = { y: { a: 0, b: 0 } };
8+
x = { y: { a: 0, b: 0, c: 0 } }; // Error
9+
10+
type A = { a: string };
11+
type B = { b: string };
12+
13+
const yy: Record<string, A> & Record<string, B> = {
14+
foo: { a: '', b: '' },
15+
};
16+
17+
18+
//// [excessPropertyCheckIntersectionWithIndexSignature.js]
19+
"use strict";
20+
// Repro from #51875
21+
var x;
22+
x = { y: { a: 0 } }; // Error
23+
x = { y: { a: 0, b: 0 } };
24+
x = { y: { a: 0, b: 0, c: 0 } }; // Error
25+
var yy = {
26+
foo: { a: '', b: '' },
27+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
=== tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts ===
2+
// Repro from #51875
3+
4+
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
5+
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 3))
6+
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 10))
7+
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 23))
8+
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 38))
9+
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 51))
10+
11+
x = { y: { a: 0 } }; // Error
12+
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 3))
13+
>y : Symbol(y, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 4, 5))
14+
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 4, 10))
15+
16+
x = { y: { a: 0, b: 0 } };
17+
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 3))
18+
>y : Symbol(y, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 5, 5))
19+
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 5, 10))
20+
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 5, 16))
21+
22+
x = { y: { a: 0, b: 0, c: 0 } }; // Error
23+
>x : Symbol(x, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 2, 3))
24+
>y : Symbol(y, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 5))
25+
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 10))
26+
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 16))
27+
>c : Symbol(c, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 22))
28+
29+
type A = { a: string };
30+
>A : Symbol(A, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 32))
31+
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 8, 10))
32+
33+
type B = { b: string };
34+
>B : Symbol(B, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 8, 23))
35+
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 9, 10))
36+
37+
const yy: Record<string, A> & Record<string, B> = {
38+
>yy : Symbol(yy, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 11, 5))
39+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
40+
>A : Symbol(A, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 6, 32))
41+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
42+
>B : Symbol(B, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 8, 23))
43+
44+
foo: { a: '', b: '' },
45+
>foo : Symbol(foo, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 11, 51))
46+
>a : Symbol(a, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 12, 10))
47+
>b : Symbol(b, Decl(excessPropertyCheckIntersectionWithIndexSignature.ts, 12, 17))
48+
49+
};
50+
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== tests/cases/compiler/excessPropertyCheckIntersectionWithIndexSignature.ts ===
2+
// Repro from #51875
3+
4+
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
5+
>x : { [x: string]: { a: 0; }; } & { [x: string]: { b: 0; }; }
6+
>x : string
7+
>a : 0
8+
>x : string
9+
>b : 0
10+
11+
x = { y: { a: 0 } }; // Error
12+
>x = { y: { a: 0 } } : { y: { a: 0; }; }
13+
>x : { [x: string]: { a: 0; }; } & { [x: string]: { b: 0; }; }
14+
>{ y: { a: 0 } } : { y: { a: 0; }; }
15+
>y : { a: 0; }
16+
>{ a: 0 } : { a: 0; }
17+
>a : 0
18+
>0 : 0
19+
20+
x = { y: { a: 0, b: 0 } };
21+
>x = { y: { a: 0, b: 0 } } : { y: { a: 0; b: 0; }; }
22+
>x : { [x: string]: { a: 0; }; } & { [x: string]: { b: 0; }; }
23+
>{ y: { a: 0, b: 0 } } : { y: { a: 0; b: 0; }; }
24+
>y : { a: 0; b: 0; }
25+
>{ a: 0, b: 0 } : { a: 0; b: 0; }
26+
>a : 0
27+
>0 : 0
28+
>b : 0
29+
>0 : 0
30+
31+
x = { y: { a: 0, b: 0, c: 0 } }; // Error
32+
>x = { y: { a: 0, b: 0, c: 0 } } : { y: { a: 0; b: 0; c: number; }; }
33+
>x : { [x: string]: { a: 0; }; } & { [x: string]: { b: 0; }; }
34+
>{ y: { a: 0, b: 0, c: 0 } } : { y: { a: 0; b: 0; c: number; }; }
35+
>y : { a: 0; b: 0; c: number; }
36+
>{ a: 0, b: 0, c: 0 } : { a: 0; b: 0; c: number; }
37+
>a : 0
38+
>0 : 0
39+
>b : 0
40+
>0 : 0
41+
>c : number
42+
>0 : 0
43+
44+
type A = { a: string };
45+
>A : { a: string; }
46+
>a : string
47+
48+
type B = { b: string };
49+
>B : { b: string; }
50+
>b : string
51+
52+
const yy: Record<string, A> & Record<string, B> = {
53+
>yy : Record<string, A> & Record<string, B>
54+
>{ foo: { a: '', b: '' },} : { foo: { a: string; b: string; }; }
55+
56+
foo: { a: '', b: '' },
57+
>foo : { a: string; b: string; }
58+
>{ a: '', b: '' } : { a: string; b: string; }
59+
>a : string
60+
>'' : ""
61+
>b : string
62+
>'' : ""
63+
64+
};
65+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @strict: true
2+
3+
// Repro from #51875
4+
5+
let x: { [x: string]: { a: 0 } } & { [x: string]: { b: 0 } };
6+
7+
x = { y: { a: 0 } }; // Error
8+
x = { y: { a: 0, b: 0 } };
9+
x = { y: { a: 0, b: 0, c: 0 } }; // Error
10+
11+
type A = { a: string };
12+
type B = { b: string };
13+
14+
const yy: Record<string, A> & Record<string, B> = {
15+
foo: { a: '', b: '' },
16+
};

0 commit comments

Comments
 (0)