More precise property-overwritten-by-spread errors (#37192)

* More precise property-overwritten-by-spread errors

Trying to do this check in getSpreadType just doesn't have enough
information, so I moved it to checkObjectLiteral, which is a better
place for issuing errors anyway.

Unfortunately, the approach is kind of expensive in that it

1. creates a new map for each property and
2. iterates over all properties of the spread type, even if it's a
union.

I have some ideas to improve (1) that might work out. I'm not sure how
bad (2) is since we're going to iterate over all properties of all
constituents of a union.

Fixes #36779

* another test and rename
This commit is contained in:
Nathan Shively-Sanders
2020-03-03 15:10:19 -08:00
committed by GitHub
parent 3046a54401
commit b481dd4d4b
7 changed files with 143 additions and 18 deletions

View File

@@ -13175,7 +13175,7 @@ namespace ts {
* this function should be called in a left folding style, with left = previous result of getSpreadType
* and right = the new element to be spread.
*/
function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean, isParentTypeNullable?: boolean): Type {
function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): Type {
if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) {
return anyType;
}
@@ -13191,16 +13191,16 @@ namespace ts {
if (left.flags & TypeFlags.Union) {
const merged = tryMergeUnionOfObjectTypeAndEmptyObject(left as UnionType, readonly);
if (merged) {
return getSpreadType(merged, right, symbol, objectFlags, readonly, isParentTypeNullable);
return getSpreadType(merged, right, symbol, objectFlags, readonly);
}
return mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly, isParentTypeNullable));
return mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly));
}
if (right.flags & TypeFlags.Union) {
const merged = tryMergeUnionOfObjectTypeAndEmptyObject(right as UnionType, readonly);
if (merged) {
return getSpreadType(left, merged, symbol, objectFlags, readonly, maybeTypeOfKind(right, TypeFlags.Nullable));
return getSpreadType(left, merged, symbol, objectFlags, readonly);
}
return mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly, maybeTypeOfKind(right, TypeFlags.Nullable)));
return mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly));
}
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) {
return left;
@@ -13264,14 +13264,6 @@ namespace ts {
result.nameType = getSymbolLinks(leftProp).nameType;
members.set(leftProp.escapedName, result);
}
else if (strictNullChecks &&
!isParentTypeNullable &&
symbol &&
!isFromSpreadAssignment(leftProp, symbol) &&
isFromSpreadAssignment(rightProp, symbol) &&
!maybeTypeOfKind(rightType, TypeFlags.Nullable)) {
error(leftProp.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(leftProp.escapedName));
}
}
else {
members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly));
@@ -16802,10 +16794,6 @@ namespace ts {
return match === -1 || discriminable.indexOf(/*searchElement*/ true, match + 1) !== -1 ? defaultValue : target.types[match];
}
function isFromSpreadAssignment(prop: Symbol, container: Symbol) {
return prop.valueDeclaration?.parent !== container.valueDeclaration;
}
/**
* A type is 'weak' if it is an object type with at least one optional property
* and no required properties, call/construct signatures or index signatures
@@ -22566,6 +22554,7 @@ namespace ts {
checkGrammarObjectLiteralExpression(node, inDestructuringPattern);
let propertiesTable: SymbolTable;
const allPropertiesTable = createSymbolTable();
let propertiesArray: Symbol[] = [];
let spread: Type = emptyObjectType;
@@ -22647,6 +22636,7 @@ namespace ts {
prop.type = type;
prop.target = member;
member = prop;
allPropertiesTable.set(prop.escapedName, prop);
}
else if (memberDecl.kind === SyntaxKind.SpreadAssignment) {
if (languageVersion < ScriptTarget.ES2015) {
@@ -22664,6 +22654,16 @@ namespace ts {
error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types);
return errorType;
}
for (const right of getPropertiesOfType(type)) {
const rightType = getTypeOfSymbol(right);
const left = allPropertiesTable.get(right.escapedName);
if (strictNullChecks &&
left &&
!maybeTypeOfKind(rightType, TypeFlags.Nullable)) {
error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName));
}
}
spread = getSpreadType(spread, type, node.symbol, objectFlags, inConstContext);
offset = i + 1;
continue;