Improve excess property checking for intersections (#32582)

* Improve excess property checking for intersections

Still a draft, the implementation needs improvement

* Use mutable isIntersection in checkTypeRelatedTo

This makes parameter lists a lot shorter. Seems like a slight
improvement, although I can revert if I change my mind.

* Fix semicolon lint

* Remove TODOOOO

* Revert "Use mutable isIntersection in checkTypeRelatedTo"

This reverts commit b8dccff2a2.
This commit is contained in:
Nathan Shively-Sanders
2019-08-06 15:03:24 -07:00
committed by GitHub
parent d00056f096
commit 480b73915f
16 changed files with 220 additions and 58 deletions

View File

@@ -12802,7 +12802,7 @@ namespace ts {
isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
const isPerformingExcessPropertyChecks = (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral);
const isPerformingExcessPropertyChecks = !isApparentIntersectionConstituent && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral);
if (isPerformingExcessPropertyChecks) {
const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined;
if (hasExcessProperties(<FreshObjectLiteralType>source, target, discriminantType, reportErrors)) {
@@ -12813,11 +12813,11 @@ namespace ts {
}
}
if (relation !== comparableRelation && !isApparentIntersectionConstituent &&
const isPerformingCommonPropertyChecks = relation !== comparableRelation && !isApparentIntersectionConstituent &&
source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType &&
target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) &&
(getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)) &&
!hasCommonProperties(source, target, isComparingJsxAttributes)) {
(getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source));
if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) {
if (reportErrors) {
const calls = getSignaturesOfType(source, SignatureKind.Call);
const constructs = getSignaturesOfType(source, SignatureKind.Construct);
@@ -12847,10 +12847,10 @@ namespace ts {
else {
if (target.flags & TypeFlags.Union) {
result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
if (result && isPerformingExcessPropertyChecks) {
if (result && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks)) {
// Validate against excess props using the original `source`
const discriminantType = findMatchingDiscriminantType(source, target as UnionType) || filterPrimitivesIfContainsNonPrimitive(target as UnionType);
if (!propertiesRelatedTo(source, discriminantType, reportErrors, /*excludedProperties*/ undefined)) {
if (!propertiesRelatedTo(source, discriminantType, reportErrors, /*excludedProperties*/ undefined, isIntersectionConstituent)) {
return Ternary.False;
}
}
@@ -12858,9 +12858,9 @@ namespace ts {
else if (target.flags & TypeFlags.Intersection) {
isIntersectionConstituent = true; // set here to affect the following trio of checks
result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors);
if (result && isPerformingExcessPropertyChecks) {
if (result && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks)) {
// Validate against excess props using the original `source`
if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined)) {
if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*isIntersectionConstituent*/ false)) {
return Ternary.False;
}
}
@@ -13186,7 +13186,7 @@ namespace ts {
return result;
}
function typeArgumentsRelatedTo(sources: ReadonlyArray<Type> = emptyArray, targets: ReadonlyArray<Type> = emptyArray, variances: ReadonlyArray<VarianceFlags> = emptyArray, reportErrors: boolean): Ternary {
function typeArgumentsRelatedTo(sources: ReadonlyArray<Type> = emptyArray, targets: ReadonlyArray<Type> = emptyArray, variances: ReadonlyArray<VarianceFlags> = emptyArray, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary {
if (sources.length !== targets.length && relation === identityRelation) {
return Ternary.False;
}
@@ -13210,10 +13210,10 @@ namespace ts {
related = relation === identityRelation ? isRelatedTo(s, t, /*reportErrors*/ false) : compareTypesIdentical(s, t);
}
else if (variance === VarianceFlags.Covariant) {
related = isRelatedTo(s, t, reportErrors);
related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent);
}
else if (variance === VarianceFlags.Contravariant) {
related = isRelatedTo(t, s, reportErrors);
related = isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent);
}
else if (variance === VarianceFlags.Bivariant) {
// In the bivariant case we first compare contravariantly without reporting
@@ -13222,16 +13222,16 @@ namespace ts {
// which is generally easier to reason about.
related = isRelatedTo(t, s, /*reportErrors*/ false);
if (!related) {
related = isRelatedTo(s, t, reportErrors);
related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent);
}
}
else {
// In the invariant case we first compare covariantly, and only when that
// succeeds do we proceed to compare contravariantly. Thus, error elaboration
// will typically be based on the covariant check.
related = isRelatedTo(s, t, reportErrors);
related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent);
if (related) {
related &= isRelatedTo(t, s, reportErrors);
related &= isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent);
}
}
if (!related) {
@@ -13377,7 +13377,7 @@ namespace ts {
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
const variances = getAliasVariances(source.aliasSymbol);
const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances);
const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, isIntersectionConstituent);
if (varianceResult !== undefined) {
return varianceResult;
}
@@ -13563,7 +13563,7 @@ namespace ts {
// type references (which are intended by be compared structurally). Obtain the variance
// information for the type parameters and relate the type arguments accordingly.
const variances = getVariances((<TypeReference>source).target);
const varianceResult = relateVariances((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, variances);
const varianceResult = relateVariances((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, variances, isIntersectionConstituent);
if (varianceResult !== undefined) {
return varianceResult;
}
@@ -13591,7 +13591,7 @@ namespace ts {
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
// Report structural errors only if we haven't reported any errors yet
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !sourceIsPrimitive;
result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined);
result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, isIntersectionConstituent);
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors);
if (result) {
@@ -13627,8 +13627,8 @@ namespace ts {
}
return Ternary.False;
function relateVariances(sourceTypeArguments: ReadonlyArray<Type> | undefined, targetTypeArguments: ReadonlyArray<Type> | undefined, variances: VarianceFlags[]) {
if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors)) {
function relateVariances(sourceTypeArguments: ReadonlyArray<Type> | undefined, targetTypeArguments: ReadonlyArray<Type> | undefined, variances: VarianceFlags[], isIntersectionConstituent: boolean) {
if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, isIntersectionConstituent)) {
return result;
}
if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) {
@@ -13756,7 +13756,7 @@ namespace ts {
if (!targetProperty) continue outer;
if (sourceProperty === targetProperty) continue;
// We compare the source property to the target in the context of a single discriminant type.
const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false);
const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, /*isIntersectionConstituent*/ false);
// If the target property could not be found, or if the properties were not related,
// then this constituent is not a match.
if (!related) {
@@ -13775,7 +13775,7 @@ namespace ts {
// Compare the remaining non-discriminant properties of each match.
let result = Ternary.True;
for (const type of matchingTypes) {
result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties);
result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, /*isIntersectionConstituent*/ false);
if (result) {
result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false);
if (result) {
@@ -13811,7 +13811,7 @@ namespace ts {
return result || properties;
}
function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary {
function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary {
const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial);
const source = getTypeOfSourceProperty(sourceProp);
if (getCheckFlags(targetProp) & CheckFlags.DeferredType && !getSymbolLinks(targetProp).type) {
@@ -13850,11 +13850,11 @@ namespace ts {
return result;
}
else {
return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors);
return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors, /*headMessage*/ undefined, isIntersectionConstituent);
}
}
function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary {
function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary {
const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp);
const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp);
if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) {
@@ -13896,7 +13896,7 @@ namespace ts {
return Ternary.False;
}
// If the target comes from a partial union prop, allow `undefined` in the target type
const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors);
const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, isIntersectionConstituent);
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
@@ -13921,7 +13921,7 @@ namespace ts {
return related;
}
function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap<true> | undefined): Ternary {
function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap<true> | undefined, isIntersectionConstituent: boolean): Ternary {
if (relation === identityRelation) {
return propertiesIdenticalTo(source, target, excludedProperties);
}
@@ -14008,7 +14008,7 @@ namespace ts {
if (!(targetProp.flags & SymbolFlags.Prototype)) {
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
if (sourceProp && sourceProp !== targetProp) {
const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors);
const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors, isIntersectionConstituent);
if (!related) {
return Ternary.False;
}