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:
Anders Hejlsberg
2022-12-16 06:59:23 -08:00
committed by GitHub
parent ff919e3b76
commit ba793e6069
6 changed files with 193 additions and 3 deletions

View File

@@ -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));

View File

@@ -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: '' },
};

View File

@@ -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: '' },
};

View File

@@ -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))
};

View File

@@ -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
>'' : ""
};

View File

@@ -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: '' },
};