From 1d2df84a771e60c925e33b6a473348585efd7765 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 4 Dec 2018 14:50:56 -0800 Subject: [PATCH] Improve excess property checking logic --- src/compiler/checker.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 982da682be0..d3ea9c170ad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11836,7 +11836,7 @@ namespace ts { if (!noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) { return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny } - if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { + if (isExcessPropertyCheckTarget(target)) { const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); if ((relation === assignableRelation || relation === definitelyAssignableRelation || relation === comparableRelation) && (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) { @@ -11849,6 +11849,9 @@ namespace ts { for (const prop of getPropertiesOfObjectType(source)) { if (shouldCheckAsExcessProperty(prop, source.symbol) && !isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) { if (reportErrors) { + // Report error in terms of object types in the target as those are the only ones + // we check in isKnownProperty. + const errorTarget = filterType(target, isExcessPropertyCheckTarget); // We know *exactly* where things went wrong when comparing the types. // Use this property as the error node as this will be more helpful in // reasoning about what went wrong. @@ -11857,7 +11860,7 @@ namespace ts { // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. // However, using an object-literal error message will be very confusing to the users so we give different a message. // TODO: Spelling suggestions for excess jsx attributes (needs new diagnostic messages) - reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(target)); + reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(errorTarget)); } else { // use the property's value declaration if the property is assigned inside the literal itself @@ -11871,17 +11874,17 @@ namespace ts { const name = propDeclaration.name!; if (isIdentifier(name)) { - suggestion = getSuggestionForNonexistentProperty(name, target); + suggestion = getSuggestionForNonexistentProperty(name, errorTarget); } } if (suggestion !== undefined) { reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, - symbolToString(prop), typeToString(target), suggestion); + symbolToString(prop), typeToString(errorTarget), suggestion); } else { reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, - symbolToString(prop), typeToString(target)); + symbolToString(prop), typeToString(errorTarget)); } } } @@ -18595,20 +18598,23 @@ namespace ts { return true; } } - else if (targetType.flags & TypeFlags.UnionOrIntersection) { + else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) { for (const t of (targetType as UnionOrIntersectionType).types) { if (isKnownProperty(t, name, isComparingJsxAttributes)) { return true; } } } - else if (targetType.flags & TypeFlags.Conditional) { - return isKnownProperty((targetType as ConditionalType).root.trueType, name, isComparingJsxAttributes) || - isKnownProperty((targetType as ConditionalType).root.falseType, name, isComparingJsxAttributes); - } return false; } + function isExcessPropertyCheckTarget(type: Type): boolean { + return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || + type.flags & TypeFlags.NonPrimitive || + type.flags & TypeFlags.Union && some((type).types, isExcessPropertyCheckTarget) || + type.flags & TypeFlags.Intersection && every((type).types, isExcessPropertyCheckTarget)); + } + function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { if (node.expression) { const type = checkExpression(node.expression, checkMode);