From 203fd9ff9e1a9923a7dc663cc738c4072b6431ee Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 22 Jul 2019 08:01:22 -0700 Subject: [PATCH] Combine multiple separate code paths --- src/compiler/checker.ts | 195 ++++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 108 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 19517b6715c..9f64e13911c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15577,7 +15577,7 @@ namespace ts { // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine const simplified = getSimplifiedType(target, /*writing*/ false); if (simplified !== target) { - inferFromTypesOnce(source, simplified); + invokeOnce(source, simplified, inferFromTypes); } else if (target.flags & TypeFlags.IndexedAccess) { const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); @@ -15586,7 +15586,7 @@ namespace ts { if (indexType.flags & TypeFlags.Instantiable) { const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); if (simplified && simplified !== target) { - inferFromTypesOnce(source, simplified); + invokeOnce(source, simplified, inferFromTypes); } } } @@ -15621,15 +15621,12 @@ namespace ts { inferFromTypes(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target)); inferFromTypes(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target)); } - else if (target.flags & TypeFlags.Union) { - inferToUnionType(source, target); - } - else if (target.flags & TypeFlags.Intersection) { - inferToMultipleTypes(source, (target).types, /*isIntersection*/ true); - } else if (target.flags & TypeFlags.Conditional && !contravariant) { const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; - inferToMultipleTypes(source, targetTypes, /*isIntersection*/ false); + inferToMultipleTypes(source, targetTypes, target.flags); + } + else if (target.flags & TypeFlags.UnionOrIntersection) { + inferToMultipleTypes(source, (target).types, target.flags); } else if (source.flags & TypeFlags.Union) { // Source is a union or intersection type, infer from each constituent type @@ -15658,50 +15655,22 @@ namespace ts { source = apparentSource; } if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { - const key = source.id + "," + target.id; - const visitCount = visited && visited.get(key); - if (visitCount !== undefined) { - inferenceCount += visitCount; - return; - } - (visited || (visited = createMap())).set(key, 0); - // If we are already processing another target type with the same associated symbol (such as - // an instantiation of the same generic type), we do not explore this target as it would yield - // no further inferences. We exclude the static side of classes from this check since it shares - // its symbol with the instance side which would lead to false positives. - const startCount = inferenceCount; - const isNonConstructorObject = target.flags & TypeFlags.Object && - !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); - const symbol = isNonConstructorObject ? target.symbol : undefined; - if (symbol) { - if (contains(symbolStack, symbol)) { - inferenceBlocked = true; - return; - } - (symbolStack || (symbolStack = [])).push(symbol); - inferFromObjectTypes(source, target); - symbolStack.pop(); - } - else { - inferFromObjectTypes(source, target); - } - visited.set(key, inferenceCount - startCount); + invokeOnce(source, target, inferFromObjectTypes); } } + } - function inferFromTypesOnce(source: Type, target: Type) { - const key = source.id + "," + target.id; - const count = visited && visited.get(key); - if (count !== undefined) { - inferenceCount += count; - } - else { - (visited || (visited = createMap())).set(key, 0); - const startCount = inferenceCount; - inferFromTypes(source, target); - visited.set(key, inferenceCount - startCount); - } + function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { + const key = source.id + "," + target.id; + const count = visited && visited.get(key); + if (count !== undefined) { + inferenceCount += count; + return; } + (visited || (visited = createMap())).set(key, 0); + const startCount = inferenceCount; + action(source, target); + visited.set(key, inferenceCount - startCount); } function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { @@ -15738,24 +15707,61 @@ namespace ts { return undefined; } - function inferToMultipleTypes(source: Type, targets: Type[], isIntersection: boolean) { - // We infer from types that are not naked type variables first so that inferences we - // make from nested naked type variables and given slightly higher priority by virtue - // of being first in the candidates array. + function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { let typeVariableCount = 0; - for (const t of targets) { - if (getInferenceInfoForType(t)) { - typeVariableCount++; + if (targetFlags & TypeFlags.Union) { + const sources = source.flags & TypeFlags.Union ? (source).types : [source]; + const matched = new Array(sources.length); + const saveInferenceBlocked = inferenceBlocked; + inferenceBlocked = false; + // First infer to types that are not naked type variables. For each source type we + // track whether inferences were made from that particular type to some target. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; + } + else { + for (let i = 0; i < sources.length; i++) { + const count = inferenceCount; + inferFromTypes(sources[i], t); + if (count !== inferenceCount) matched[i] = true; + } + } } - else { - inferFromTypes(source, t); + // If the target has a single naked type variable and inference wasn't blocked (meaning + // we explored the types fully), create a union of the source types from which no inferences + // have been made so far and infer from that union to the naked type variable. + if (typeVariableCount === 1 && !inferenceBlocked) { + const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); + if (unmatched.length) { + const s = getUnionType(unmatched); + for (const t of targets) { + if (getInferenceInfoForType(t)) { + inferFromTypes(s, t); + } + } + } + } + inferenceBlocked = inferenceBlocked || saveInferenceBlocked; + } + else { + // We infer from types that are not naked type variables first so that inferences we + // make from nested naked type variables and given slightly higher priority by virtue + // of being first in the candidates array. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; + } + else { + inferFromTypes(source, t); + } } } // Inferences directly to naked type variables are given lower priority as they are // less specific. For example, when inferring from Promise to T | Promise, // we want to infer string for T, not Promise | string. For intersection types // we only infer to single naked type variables. - if (isIntersection ? typeVariableCount === 1 : typeVariableCount !== 0) { + if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { const savePriority = priority; priority |= InferencePriority.NakedTypeVariable; for (const t of targets) { @@ -15767,55 +15773,6 @@ namespace ts { } } - function inferToUnionType(source: Type, target: UnionType) { - const sources = source.flags & TypeFlags.Union ? (source).types : [source]; - const matched = new Array(sources.length); - let typeVariableCount = 0; - const saveInferenceBlocked = inferenceBlocked; - inferenceBlocked = false; - // First infer to types that are not naked type variables. For each source type we - // track whether inferences were made from that particular type to some target. - for (const t of target.types) { - if (getInferenceInfoForType(t)) { - typeVariableCount++; - } - else { - for (let i = 0; i < sources.length; i++) { - const count = inferenceCount; - inferFromTypes(sources[i], t); - if (count !== inferenceCount) matched[i] = true; - } - } - } - // If there are naked type variables in the target, create a union of the source types - // from which no inferences have been made so far and infer from that union to each naked - // type variable. If there is more than one naked type variable, or if inference was blocked - // (meaning we didn't explore the types fully), give lower priority to the inferences as - // they are less specific. - if (typeVariableCount === 1 && !inferenceBlocked) { - const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); - if (unmatched.length) { - const s = getUnionType(unmatched); - for (const t of target.types) { - if (getInferenceInfoForType(t)) { - inferFromTypes(s, t); - } - } - } - } - inferenceBlocked = inferenceBlocked || saveInferenceBlocked; - if (typeVariableCount > 0) { - const savePriority = priority; - priority |= InferencePriority.NakedTypeVariable; - for (const t of target.types) { - if (getInferenceInfoForType(t)) { - inferFromTypes(source, t); - } - } - priority = savePriority; - } - } - function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { if (constraintType.flags & TypeFlags.Union) { let result = false; @@ -15873,6 +15830,28 @@ namespace ts { } function inferFromObjectTypes(source: Type, target: Type) { + // If we are already processing another target type with the same associated symbol (such as + // an instantiation of the same generic type), we do not explore this target as it would yield + // no further inferences. We exclude the static side of classes from this check since it shares + // its symbol with the instance side which would lead to false positives. + const isNonConstructorObject = target.flags & TypeFlags.Object && + !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); + const symbol = isNonConstructorObject ? target.symbol : undefined; + if (symbol) { + if (contains(symbolStack, symbol)) { + inferenceBlocked = true; + return; + } + (symbolStack || (symbolStack = [])).push(symbol); + inferFromObjectTypesWorker(source, target); + symbolStack.pop(); + } + else { + inferFromObjectTypesWorker(source, target); + } + } + + function inferFromObjectTypesWorker(source: Type, target: Type) { if (isGenericMappedType(source) && isGenericMappedType(target)) { // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer // from S to T and from X to Y.