@@ -14706,17 +14706,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
14706
14706
isInJSFile(signature.declaration));
14707
14707
}
14708
14708
14709
- function getBaseSignature(signature: Signature) {
14710
- const typeParameters = signature.typeParameters;
14711
- if (typeParameters) {
14712
- if (signature.baseSignatureCache) {
14713
- return signature.baseSignatureCache;
14714
- }
14715
- return signature.baseSignatureCache = instantiateSignature(signature, getTypeParameterConstraintMapper(typeParameters), /*eraseTypeParameters*/ true);
14716
- }
14717
- return signature;
14718
- }
14719
-
14720
14709
function getTypeParameterConstraintMapper(typeParameters: readonly TypeParameter[]) {
14721
14710
const typeEraser = createTypeEraser(typeParameters);
14722
14711
const baseConstraintMapper = createTypeMapper(typeParameters, map(typeParameters, tp => getConstraintOfTypeParameter(tp) || unknownType));
@@ -23581,7 +23570,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
23581
23570
}
23582
23571
23583
23572
function cloneInferenceContext<T extends InferenceContext | undefined>(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined {
23584
- return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes);
23573
+ const newContext = context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes);
23574
+ if (context && context.freeTypeVariables) {
23575
+ newContext.freeTypeVariables = map(context.freeTypeVariables, cloneInferenceInfo);
23576
+ newContext.freeTypeVariableSourceSignatures = new Map(ts.mapIterator(context.freeTypeVariableSourceSignatures!.entries(), ([k, v]) => [k, new Map(v.entries())]));
23577
+ }
23578
+ return newContext;
23585
23579
}
23586
23580
23587
23581
function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext {
@@ -23621,9 +23615,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
23621
23615
if (!context.freeTypeVariables) return undefined;
23622
23616
// make a seperate mapper for every free type variable so free type variables constrained to one another are instantiated
23623
23617
// with any intermediate inferences found rather than the constraints
23618
+ const baseMapper = getTypeParameterConstraintMapper(map(context.freeTypeVariables, i => i.typeParameter));
23624
23619
return context.freeTypeVariables.reduceRight(
23625
- (previous, i) => combineTypeMappers(makeDeferredTypeMapper([i.typeParameter], [() => getInferredType(context, i)]), previous),
23626
- getTypeParameterConstraintMapper(map(context.freeTypeVariables, i => i.typeParameter))
23620
+ (previous, i) => combineTypeMappers(makeDeferredTypeMapper([i.typeParameter], [() => hasInferenceCandidatesOrDefault(i) ? getInferredType(context, i) : instantiateType(i.typeParameter, baseMapper )]), previous),
23621
+ baseMapper
23627
23622
);
23628
23623
}
23629
23624
@@ -24087,27 +24082,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
24087
24082
let sourceStack: object[];
24088
24083
let targetStack: object[];
24089
24084
let expandingFlags = ExpandingFlags.None;
24085
+ let useOnlyCachedAlternativeRoutes = false;
24090
24086
inferFromTypes(originalSource, originalTarget);
24091
24087
// second inference stage to the non-fixing-mapped target ensures we record mappings for
24092
24088
// the free type variables in the context which come from signature inferences,
24093
24089
// which we need so we can map the free type variables entirely out of the resulting inferred types
24094
24090
// once inference is complete with a better mapping than their constraint.
24095
- forEachAlternative((context, oldContext) => {
24091
+ useOnlyCachedAlternativeRoutes = true;
24092
+ // For the followup passes, by using only cached alternative routes (and bailing on those alternatives which lack a cache entry),
24093
+ // we can avoid creating a power set of combinations by only exploring the set of inferences where an overload is only influenced by itself, and
24094
+ // not by sibling signatures from a prior pass.
24095
+
24096
+ const group = forkInferenceContext();
24097
+ spawnAlternativeInferenceContext(group, () => {
24096
24098
if (length(context.freeTypeVariables)) {
24097
24099
inferFromTypes(originalSource, instantiateType(originalTarget, context.nonFixingMapper))
24098
- mergeInferenceContexts(context, oldContext, /*clone*/ true);
24099
24100
}
24100
- });
24101
-
24102
- function forEachAlternative(action: (context: InferenceContext, oldContext: InferenceContext) => void) {
24103
- const ctxList = context.alternatives ? context.alternatives : [context];
24104
- const oldContext = context;
24105
- for (const ctx of ctxList) {
24106
- context = ctx;
24107
- action(context, oldContext);
24108
- }
24109
- context = oldContext;
24110
- }
24101
+ }, /*fork*/ false);
24102
+ joinInferenceContext(group);
24111
24103
24112
24104
function inferFromTypes(source: Type, target: Type): void {
24113
24105
if (!couldContainTypeVariables(target)) {
@@ -24212,6 +24204,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
24212
24204
if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType) {
24213
24205
return;
24214
24206
}
24207
+ if (useOnlyCachedAlternativeRoutes && source === target) {
24208
+ // refrain from inferring a type to itself during a followup pass - it won't add extra information
24209
+ return;
24210
+ }
24215
24211
if (!inference.isFixed) {
24216
24212
if (inference.priority === undefined || priority < inference.priority) {
24217
24213
inference.candidates = undefined;
@@ -24240,15 +24236,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
24240
24236
}
24241
24237
}
24242
24238
inferencePriority = Math.min(inferencePriority, priority);
24243
-
24244
- // const constraint = getConstraintOfType(target);
24245
- // if (constraint) {
24246
- // // By adding the constraint as an extra inference source, we allow type parameters within constraints to tie one
24247
- // // another together and create inference locations that are otherwise difficult. It basically lets a user capture a
24248
- // // whole (or part of a) parameter type into a type variable, and destructure it for use in other parameters and the
24249
- // // return type positions.
24250
- // inferFromTypes(source, constraint);
24251
- // }
24252
24239
return;
24253
24240
}
24254
24241
// Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
@@ -24813,20 +24800,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
24813
24800
24814
24801
/**
24815
24802
* Performs the given action in every alternative inference context currently under consideration,
24816
- * and
24803
+ * and add the resulting context(s) to a new resultant context list.
24804
+ *
24805
+ * Call multiple times to create multiple independent forks of the existing inference engine state.
24806
+ * Call once with fork `false` to simply to do and action for every existing branch of the inference
24807
+ * engine state, while handling if those actions produce further forks of the state.
24817
24808
*/
24818
- function spawnAlternativeInferenceContext(group: InferenceContext[], action: () => void) {
24819
- const oldContext: InferenceContext = context;
24809
+ function spawnAlternativeInferenceContext(group: InferenceContext[], action: () => void, fork = true ) {
24810
+ const oldContext = context;
24820
24811
// We want to keep a flat list of alternatives in the top-level inference context;
24821
24812
// so we perform our new forking action for each existing alternative, and then when
24822
24813
// we're done making all the new forks, we'll replace the existing alternative list
24823
24814
// with the new one.
24824
- (oldContext.alternatives || [oldContext] ).forEach(alternative => {
24815
+ flattenInferenceContextAlternatives(context ).forEach(alternative => {
24825
24816
// It's intentional that the `clone` here doesn't clone the `alternatives` - we want this
24826
24817
// fork to be ignorant to the other forks we'll be trying - only the outermost context
24827
24818
// should retain knowledge of the many linked inference attempts we're making.
24828
- const newContext = cloneInferenceContext(alternative);
24829
- mergeInferenceContexts(newContext, oldContext, /*clone*/ true);
24819
+ const newContext = fork ? cloneInferenceContext(alternative) : alternative;
24830
24820
context = newContext;
24831
24821
action();
24832
24822
context = oldContext;
@@ -24843,8 +24833,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
24843
24833
// Only one alternative was tried - inline it as the new outer context.
24844
24834
mergeInferenceContexts(context, group[0]);
24845
24835
context.freeTypeVariables = group[0].freeTypeVariables;
24836
+ context.freeTypeVariableSourceSignatures = group[0].freeTypeVariableSourceSignatures;
24846
24837
context.inferredTypeParameters = group[0].inferredTypeParameters;
24847
24838
context.flags = group[0].flags;
24839
+ context.alternatives = undefined;
24848
24840
return;
24849
24841
}
24850
24842
context.alternatives = group;
@@ -24885,6 +24877,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
24885
24877
// inference from a set of overloads to a single signature - produce matches for _every_ overload
24886
24878
const group = forkInferenceContext();
24887
24879
for (let i = 0; i < sourceLen; i++) {
24880
+ if (useOnlyCachedAlternativeRoutes && !context.freeTypeVariableSourceSignatures?.get(last(sourceStack))?.get(sourceSignatures[i])) {
24881
+ continue; // Doing a follow-up pass - ignore new alternatives that don't follow the same "route" as the first pass
24882
+ }
24888
24883
spawnAlternativeInferenceContext(group, () =>
24889
24884
inferFromSignature(sourceSignatures[i], targetSignatures[0])
24890
24885
);
@@ -24904,17 +24899,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
24904
24899
function inferFromSignature(source: Signature, target: Signature) {
24905
24900
target = getErasedSignature(target);
24906
24901
if (source.typeParameters) {
24907
- if (some(context.freeTypeVariables, i => some(source.typeParameters, p => p.symbol === i.typeParameter.symbol))) {
24908
- // already inferred from this sigature generically and then recured, infer to constraints this time
24909
- source = getBaseSignature(source);
24902
+ // Rather than getting the "base" signature, add the type parameters as free type variables to the inference list
24903
+ // These get saved off so we can infer from the expression type to them after we apply contextual types, and then
24904
+ // apply those mappings to our inference results.
24905
+ // Create a fresh clone of the source with a fresh clone of the source's type parameters, so recursive invocations
24906
+ // don't use the same type variable inferences (the noop mapper simply forces the clone)
24907
+ // BUT we want to retain the same signature and type parameters between inference passes at the same depth,
24908
+ // so we cache this signature on the context
24909
+ let cache = (context.freeTypeVariableSourceSignatures ||= new Map()).get(last(sourceStack));
24910
+ if (!cache) {
24911
+ context.freeTypeVariableSourceSignatures.set(last(sourceStack), cache = (new Map()));
24912
+ }
24913
+ const cached = cache.get(source);
24914
+ if (cached) {
24915
+ source = cached; // The relevant free type variables should already be in the context, no need to add more
24910
24916
}
24911
24917
else {
24912
- // Rather than getting the "base" signature, add the type parameters as free type variables to the inference list
24913
- // These get saved off so we can infer from the expression type to them after we apply contextual types, and then
24914
- // apply those mappings to our inference results.
24915
- // Create a fresh clone of the source with a fresh clone of the source's type parameters, so recursive invocations
24916
- // don't use the same type variable inferences (the noop mapper simply forces the clone)
24918
+ const originalSignature = source;
24917
24919
source = instantiateSignature(source, makeUnaryTypeMapper(unknownType, unknownType));
24920
+ cache.set(originalSignature, source);
24918
24921
(context.freeTypeVariables ||= []).push(...map(source.typeParameters!, createInferenceInfo));
24919
24922
}
24920
24923
}
0 commit comments