Reuse "getBestMatchingType" logic during elaboration to allow for more specific elaborations (#35278)

* Filter target union during elaboration to allow for more specific elaborations

* Reuse best match logic instead

* Update user baselines (#48)

Co-authored-by: TypeScript Bot <ts_bot@rcavanaugh.com>
This commit is contained in:
Wesley Wigham
2020-01-02 13:14:08 -08:00
committed by GitHub
parent bb306a757b
commit 291ab63a9b
20 changed files with 830 additions and 199 deletions

View File

@@ -13791,6 +13791,19 @@ namespace ts {
return false;
}
function getBestMatchIndexedAccessTypeOrUndefined(source: Type, target: Type, nameType: Type) {
const idx = getIndexedAccessTypeOrUndefined(target, nameType);
if (idx) {
return idx;
}
if (target.flags & TypeFlags.Union) {
const best = getBestMatchingType(source, target as UnionType);
if (best) {
return getIndexedAccessTypeOrUndefined(best, nameType);
}
}
}
type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>;
/**
* For every element returned from the iterator, checks that element to issue an error on a property of that element's type
@@ -13809,7 +13822,7 @@ namespace ts {
let reportedError = false;
for (let status = iterator.next(); !status.done; status = iterator.next()) {
const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value;
const targetPropType = getIndexedAccessTypeOrUndefined(target, nameType);
const targetPropType = getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType);
if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables
const sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType);
if (sourcePropType && !checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) {
@@ -14942,7 +14955,7 @@ namespace ts {
let reducedTarget = target;
let checkTypes: Type[] | undefined;
if (target.flags & TypeFlags.Union) {
reducedTarget = findMatchingDiscriminantType(source, <UnionType>target) || filterPrimitivesIfContainsNonPrimitive(<UnionType>target);
reducedTarget = findMatchingDiscriminantType(source, <UnionType>target, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(<UnionType>target);
checkTypes = reducedTarget.flags & TypeFlags.Union ? (<UnionType>reducedTarget).types : [reducedTarget];
}
for (const prop of getPropertiesOfType(source)) {
@@ -15034,103 +15047,12 @@ namespace ts {
}
}
if (reportErrors) {
const bestMatchingType =
findMatchingDiscriminantType(source, target) ||
findMatchingTypeReferenceOrTypeAliasReference(source, target) ||
findBestTypeForObjectLiteral(source, target) ||
findBestTypeForInvokable(source, target) ||
findMostOverlappyType(source, target);
const bestMatchingType = getBestMatchingType(source, target, isRelatedTo);
isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true);
}
return Ternary.False;
}
function findMatchingTypeReferenceOrTypeAliasReference(source: Type, unionTarget: UnionOrIntersectionType) {
const sourceObjectFlags = getObjectFlags(source);
if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) {
return find(unionTarget.types, target => {
if (target.flags & TypeFlags.Object) {
const overlapObjFlags = sourceObjectFlags & getObjectFlags(target);
if (overlapObjFlags & ObjectFlags.Reference) {
return (source as TypeReference).target === (target as TypeReference).target;
}
if (overlapObjFlags & ObjectFlags.Anonymous) {
return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol;
}
}
return false;
});
}
}
function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) {
if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && forEachType(unionTarget, isArrayLikeType)) {
return find(unionTarget.types, t => !isArrayLikeType(t));
}
}
function findBestTypeForInvokable(source: Type, unionTarget: UnionOrIntersectionType) {
let signatureKind = SignatureKind.Call;
const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 ||
(signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0);
if (hasSignatures) {
return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0);
}
}
function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) {
let bestMatch: Type | undefined;
let matchingCount = 0;
for (const target of unionTarget.types) {
const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]);
if (overlap.flags & TypeFlags.Index) {
// perfect overlap of keys
bestMatch = target;
matchingCount = Infinity;
}
else if (overlap.flags & TypeFlags.Union) {
// We only want to account for literal types otherwise.
// If we have a union of index types, it seems likely that we
// needed to elaborate between two generic mapped types anyway.
const len = length(filter((overlap as UnionType).types, isUnitType));
if (len >= matchingCount) {
bestMatch = target;
matchingCount = len;
}
}
else if (isUnitType(overlap) && 1 >= matchingCount) {
bestMatch = target;
matchingCount = 1;
}
}
return bestMatch;
}
function filterPrimitivesIfContainsNonPrimitive(type: UnionType) {
if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) {
const result = filterType(type, t => !(t.flags & TypeFlags.Primitive));
if (!(result.flags & TypeFlags.Never)) {
return result;
}
}
return type;
}
// Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly
function findMatchingDiscriminantType(source: Type, target: Type) {
if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) {
const sourceProperties = getPropertiesOfType(source);
if (sourceProperties) {
const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target);
if (sourcePropertiesFiltered) {
return discriminateTypeByDiscriminableItems(<UnionType>target, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo);
}
}
}
return undefined;
}
function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean): Ternary {
let result = Ternary.True;
const targetTypes = target.types;
@@ -16303,6 +16225,14 @@ namespace ts {
}
}
function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) {
return findMatchingDiscriminantType(source, target, isRelatedTo) ||
findMatchingTypeReferenceOrTypeAliasReference(source, target) ||
findBestTypeForObjectLiteral(source, target) ||
findBestTypeForInvokable(source, target) ||
findMostOverlappyType(source, target);
}
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary): Type | undefined;
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type): Type;
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type) {
@@ -36663,6 +36593,91 @@ namespace ts {
}
return false;
}
function findMatchingTypeReferenceOrTypeAliasReference(source: Type, unionTarget: UnionOrIntersectionType) {
const sourceObjectFlags = getObjectFlags(source);
if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) {
return find(unionTarget.types, target => {
if (target.flags & TypeFlags.Object) {
const overlapObjFlags = sourceObjectFlags & getObjectFlags(target);
if (overlapObjFlags & ObjectFlags.Reference) {
return (source as TypeReference).target === (target as TypeReference).target;
}
if (overlapObjFlags & ObjectFlags.Anonymous) {
return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol;
}
}
return false;
});
}
}
function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) {
if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && forEachType(unionTarget, isArrayLikeType)) {
return find(unionTarget.types, t => !isArrayLikeType(t));
}
}
function findBestTypeForInvokable(source: Type, unionTarget: UnionOrIntersectionType) {
let signatureKind = SignatureKind.Call;
const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 ||
(signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0);
if (hasSignatures) {
return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0);
}
}
function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) {
let bestMatch: Type | undefined;
let matchingCount = 0;
for (const target of unionTarget.types) {
const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]);
if (overlap.flags & TypeFlags.Index) {
// perfect overlap of keys
bestMatch = target;
matchingCount = Infinity;
}
else if (overlap.flags & TypeFlags.Union) {
// We only want to account for literal types otherwise.
// If we have a union of index types, it seems likely that we
// needed to elaborate between two generic mapped types anyway.
const len = length(filter((overlap as UnionType).types, isUnitType));
if (len >= matchingCount) {
bestMatch = target;
matchingCount = len;
}
}
else if (isUnitType(overlap) && 1 >= matchingCount) {
bestMatch = target;
matchingCount = 1;
}
}
return bestMatch;
}
function filterPrimitivesIfContainsNonPrimitive(type: UnionType) {
if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) {
const result = filterType(type, t => !(t.flags & TypeFlags.Primitive));
if (!(result.flags & TypeFlags.Never)) {
return result;
}
}
return type;
}
// Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly
function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary) {
if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) {
const sourceProperties = getPropertiesOfType(source);
if (sourceProperties) {
const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target);
if (sourcePropertiesFiltered) {
return discriminateTypeByDiscriminableItems(<UnionType>target, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo);
}
}
}
return undefined;
}
}
function isNotAccessor(declaration: Declaration): boolean {