@@ -20937,26 +20937,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
20937
20937
result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
20938
20938
}
20939
20939
}
20940
- // For certain combinations involving intersections and optional, excess, or mismatched properties we need
20941
- // an extra property check where the intersection is viewed as a single object. The following are motivating
20942
- // examples that all should be errors, but aren't without this extra property check:
20940
+ // When the target is an intersection we need an extra property check in order to detect nested excess
20941
+ // properties and nested weak types. The following are motivating examples that all should be errors, but
20942
+ // aren't without this extra property check:
20943
20943
//
20944
20944
// let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property
20945
20945
//
20946
20946
// declare let wrong: { a: { y: string } };
20947
20947
// let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type
20948
20948
//
20949
- // function foo<T extends object>(x: { a?: string }, y: T & { a: boolean }) {
20950
- // x = y; // Mismatched property in source intersection
20951
- // }
20952
- if (result && !(intersectionState & IntersectionState.Target) && (
20953
- target.flags & TypeFlags.Intersection && !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection) ||
20954
- isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) {
20955
- result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None);
20949
+ if (result && !(intersectionState & IntersectionState.Target) &&
20950
+ target.flags & TypeFlags.Intersection && !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
20951
+ result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, IntersectionState.None);
20956
20952
if (result && isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) {
20957
20953
result &= indexSignaturesRelatedTo(source, target, /*sourceIsPrimitive*/ false, reportErrors, IntersectionState.None);
20958
20954
}
20959
20955
}
20956
+ // When the source is an intersection we need an extra check of any optional properties in the target to
20957
+ // detect possible mismatched property types. For example:
20958
+ //
20959
+ // function foo<T extends object>(x: { a?: string }, y: T & { a: boolean }) {
20960
+ // x = y; // Mismatched property in source intersection
20961
+ // }
20962
+ //
20963
+ else if (result && isNonGenericObjectType(target) && !isArrayOrTupleType(target) && some(getPropertiesOfType(target), p => !!(p.flags & SymbolFlags.Optional)) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType))) {
20964
+ result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ true, intersectionState);
20965
+ }
20960
20966
}
20961
20967
if (result) {
20962
20968
resetErrorInfo(saveErrorInfo);
@@ -21463,7 +21469,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21463
21469
if (sourceFlags & (TypeFlags.Object | TypeFlags.Intersection) && targetFlags & TypeFlags.Object) {
21464
21470
// Report structural errors only if we haven't reported any errors yet
21465
21471
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive;
21466
- result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState);
21472
+ result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, intersectionState);
21467
21473
if (result) {
21468
21474
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors, intersectionState);
21469
21475
if (result) {
@@ -21638,7 +21644,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21638
21644
// Compare the remaining non-discriminant properties of each match.
21639
21645
let result = Ternary.True;
21640
21646
for (const type of matchingTypes) {
21641
- result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None);
21647
+ result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, /*optionalsOnly*/ false, IntersectionState.None);
21642
21648
if (result) {
21643
21649
result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false, IntersectionState.None);
21644
21650
if (result) {
@@ -21805,7 +21811,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21805
21811
// No array like or unmatched property error - just issue top level error (errorInfo = undefined)
21806
21812
}
21807
21813
21808
- function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, intersectionState: IntersectionState): Ternary {
21814
+ function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, optionalsOnly: boolean, intersectionState: IntersectionState): Ternary {
21809
21815
if (relation === identityRelation) {
21810
21816
return propertiesIdenticalTo(source, target, excludedProperties);
21811
21817
}
@@ -21940,7 +21946,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21940
21946
const numericNamesOnly = isTupleType(source) && isTupleType(target);
21941
21947
for (const targetProp of excludeProperties(properties, excludedProperties)) {
21942
21948
const name = targetProp.escapedName;
21943
- if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) {
21949
+ if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length") && (!optionalsOnly || targetProp.flags & SymbolFlags.Optional) ) {
21944
21950
const sourceProp = getPropertyOfType(source, name);
21945
21951
if (sourceProp && sourceProp !== targetProp) {
21946
21952
const related = propertyRelatedTo(source, target, sourceProp, targetProp, getNonMissingTypeOfSymbol, reportErrors, intersectionState, relation === comparableRelation);
0 commit comments