Infer between closely matching types in unions and intersections
This commit is contained in:
parent
e8966ce033
commit
dc415c5c5e
1 changed files with 56 additions and 60 deletions
|
@ -15497,37 +15497,27 @@ namespace ts {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Find each source constituent type that has an identically matching target constituent
|
// First, infer between exactly matching source and target constituents and remove
|
||||||
// type, and for each such type infer from the type to itself. When inferring from a
|
// the matching types. Types exactly match when they are identical or, in union
|
||||||
// type to itself we effectively find all type parameter occurrences within that type
|
// types, when the source is a literal and the target is the corresponding primitive.
|
||||||
// and infer themselves as their type arguments. We have special handling for numeric
|
const matching = target.flags & TypeFlags.Union ? isTypeOrBaseExactlyMatchedBy : isTypeExactlyMatchedBy;
|
||||||
// and string literals because the number and string types are not represented as unions
|
const [tempSources, tempTargets] = inferFromMatchingTypes((<UnionOrIntersectionType>source).types, (<UnionOrIntersectionType>target).types, matching);
|
||||||
// of all their possible values.
|
// Next, infer between closely matching source and target constituents and remove
|
||||||
let matchingTypes: Type[] | undefined;
|
// the matching types. Types closely match when they are instantiations of the same
|
||||||
for (const t of (<UnionOrIntersectionType>source).types) {
|
// object type or instantiations of the same type alias.
|
||||||
const matched = findMatchedType(t, <UnionOrIntersectionType>target);
|
const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy);
|
||||||
if (matched) {
|
if (sources.length === 0 || targets.length === 0) {
|
||||||
(matchingTypes || (matchingTypes = [])).push(matched);
|
|
||||||
inferFromTypes(matched, matched);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Next, to improve the quality of inferences, reduce the source and target types by
|
|
||||||
// removing the identically matched constituents. For example, when inferring from
|
|
||||||
// 'string | string[]' to 'string | T' we reduce the types to 'string[]' and 'T'.
|
|
||||||
if (matchingTypes) {
|
|
||||||
const s = removeTypesFromUnionOrIntersection(<UnionOrIntersectionType>source, matchingTypes);
|
|
||||||
const t = removeTypesFromUnionOrIntersection(<UnionOrIntersectionType>target, matchingTypes);
|
|
||||||
if (!(s && t)) return;
|
|
||||||
source = s;
|
|
||||||
target = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (target.flags & TypeFlags.Union && !(target.flags & TypeFlags.EnumLiteral) || target.flags & TypeFlags.Intersection) {
|
|
||||||
const matched = findMatchedType(source, <UnionOrIntersectionType>target);
|
|
||||||
if (matched) {
|
|
||||||
inferFromTypes(matched, matched);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
source = source.flags & TypeFlags.Union ? getUnionType(sources) : getIntersectionType(sources);
|
||||||
|
target = target.flags & TypeFlags.Union ? getUnionType(targets) : getIntersectionType(targets);
|
||||||
|
}
|
||||||
|
else if (target.flags & TypeFlags.Union && !(target.flags & TypeFlags.EnumLiteral) || target.flags & TypeFlags.Intersection) {
|
||||||
|
// This block of code is an optimized version of the block above for the simpler case
|
||||||
|
// of a singleton source type.
|
||||||
|
const matching = target.flags & TypeFlags.Union ? isTypeOrBaseExactlyMatchedBy : isTypeExactlyMatchedBy;
|
||||||
|
if (inferFromMatchingType(source, (<UnionOrIntersectionType>target).types, matching)) return;
|
||||||
|
if (inferFromMatchingType(source, (<UnionOrIntersectionType>target).types, isTypeCloselyMatchedBy)) return;
|
||||||
}
|
}
|
||||||
else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
|
else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
|
||||||
target = getActualTypeVariable(target);
|
target = getActualTypeVariable(target);
|
||||||
|
@ -15675,6 +15665,35 @@ namespace ts {
|
||||||
visited.set(key, inferenceCount - startCount);
|
visited.set(key, inferenceCount - startCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function inferFromMatchingType(source: Type, targets: Type[], matches: (s: Type, t: Type) => boolean) {
|
||||||
|
let matched = false;
|
||||||
|
for (const t of targets) {
|
||||||
|
if (matches(source, t)) {
|
||||||
|
inferFromTypes(source, t);
|
||||||
|
matched = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] {
|
||||||
|
let matchedSources: Type[] | undefined;
|
||||||
|
let matchedTargets: Type[] | undefined;
|
||||||
|
for (const t of targets) {
|
||||||
|
for (const s of sources) {
|
||||||
|
if (matches(s, t)) {
|
||||||
|
inferFromTypes(s, t);
|
||||||
|
matchedSources = appendIfUnique(matchedSources, s);
|
||||||
|
matchedTargets = appendIfUnique(matchedTargets, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources,
|
||||||
|
matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) {
|
function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) {
|
||||||
const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length;
|
const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length;
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
|
@ -15955,47 +15974,24 @@ namespace ts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMatchableType(type: Type) {
|
function isNonObjectOrAnonymousType(type: Type) {
|
||||||
// We exclude non-anonymous object types because some frameworks (e.g. Ember) rely on the ability to
|
// We exclude non-anonymous object types because some frameworks (e.g. Ember) rely on the ability to
|
||||||
// infer between types that don't witness their type variables. Such types would otherwise be eliminated
|
// infer between types that don't witness their type variables. Such types would otherwise be eliminated
|
||||||
// because they appear identical.
|
// because they appear identical.
|
||||||
return !(type.flags & TypeFlags.Object) || !!(getObjectFlags(type) & ObjectFlags.Anonymous);
|
return !(type.flags & TypeFlags.Object) || !!(getObjectFlags(type) & ObjectFlags.Anonymous);
|
||||||
}
|
}
|
||||||
|
|
||||||
function typeMatchedBySomeType(type: Type, types: Type[]): boolean {
|
function isTypeExactlyMatchedBy(s: Type, t: Type) {
|
||||||
for (const t of types) {
|
return s === t || isNonObjectOrAnonymousType(s) && isNonObjectOrAnonymousType(t) && isTypeIdenticalTo(s, t);
|
||||||
if (t === type || isMatchableType(t) && isMatchableType(type) && isTypeIdenticalTo(t, type)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function findMatchedType(type: Type, target: UnionOrIntersectionType) {
|
function isTypeOrBaseExactlyMatchedBy(s: Type, t: Type) {
|
||||||
if (typeMatchedBySomeType(type, target.types)) {
|
return isTypeExactlyMatchedBy(s, t) || !!(s.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) && isTypeIdenticalTo(getBaseTypeOfLiteralType(s), t);
|
||||||
return type;
|
|
||||||
}
|
|
||||||
if (type.flags & (TypeFlags.NumberLiteral | TypeFlags.StringLiteral) && target.flags & TypeFlags.Union) {
|
|
||||||
const base = getBaseTypeOfLiteralType(type);
|
|
||||||
if (typeMatchedBySomeType(base, target.types)) {
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function isTypeCloselyMatchedBy(s: Type, t: Type) {
|
||||||
* Return a new union or intersection type computed by removing a given set of types
|
return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol ||
|
||||||
* from a given union or intersection type.
|
s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol);
|
||||||
*/
|
|
||||||
function removeTypesFromUnionOrIntersection(type: UnionOrIntersectionType, typesToRemove: Type[]) {
|
|
||||||
const reducedTypes: Type[] = [];
|
|
||||||
for (const t of type.types) {
|
|
||||||
if (!typeMatchedBySomeType(t, typesToRemove)) {
|
|
||||||
reducedTypes.push(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reducedTypes.length ? type.flags & TypeFlags.Union ? getUnionType(reducedTypes) : getIntersectionType(reducedTypes) : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasPrimitiveConstraint(type: TypeParameter): boolean {
|
function hasPrimitiveConstraint(type: TypeParameter): boolean {
|
||||||
|
|
Loading…
Reference in a new issue