8000 Fix bugs with leaked type parameters and overinstantiated circular de… · weswigham/TypeScript@16c969c · GitHub
[go: up one dir, main page]

Skip to content

Commit 16c969c

Browse files
committed
Fix bugs with leaked type parameters and overinstantiated circular default inferences
1 parent b7849a1 commit 16c969c

16 files changed

+115
-414
lines changed

src/compiler/checker.ts

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14706,17 +14706,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1470614706
isInJSFile(signature.declaration));
1470714707
}
1470814708

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-
1472014709
function getTypeParameterConstraintMapper(typeParameters: readonly TypeParameter[]) {
1472114710
const typeEraser = createTypeEraser(typeParameters);
1472214711
const baseConstraintMapper = createTypeMapper(typeParameters, map(typeParameters, tp => getConstraintOfTypeParameter(tp) || unknownType));
@@ -23581,7 +23570,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2358123570
}
2358223571

2358323572
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;
2358523579
}
2358623580

2358723581
function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext {
@@ -23621,9 +23615,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2362123615
if (!context.freeTypeVariables) return undefined;
2362223616
// make a seperate mapper for every free type variable so free type variables constrained to one another are instantiated
2362323617
// with any intermediate inferences found rather than the constraints
23618+
const baseMapper = getTypeParameterConstraintMapper(map(context.freeTypeVariables, i => i.typeParameter));
2362423619
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
2362723622
);
2362823623
}
2362923624

@@ -24087,27 +24082,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2408724082
let sourceStack: object[];
2408824083
let targetStack: object[];
2408924084
let expandingFlags = ExpandingFlags.None;
24085+
let useOnlyCachedAlternativeRoutes = false;
2409024086
inferFromTypes(originalSource, originalTarget);
2409124087
// second inference stage to the non-fixing-mapped target ensures we record mappings for
2409224088
// the free type variables in the context which come from signature inferences,
2409324089
// which we need so we can map the free type variables entirely out of the resulting inferred types
2409424090
// 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, () => {
2409624098
if (length(context.freeTypeVariables)) {
2409724099
inferFromTypes(originalSource, instantiateType(originalTarget, context.nonFixingMapper))
24098-
mergeInferenceContexts(context, oldContext, /*clone*/ true);
2409924100
}
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);
2411124103

2411224104
function inferFromTypes(source: Type, target: Type): void {
2411324105
if (!couldContainTypeVariables(target)) {
@@ -24212,6 +24204,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2421224204
if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType) {
2421324205
return;
2421424206
}
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+
}
2421524211
if (!inference.isFixed) {
2421624212
if (inference.priority === undefined || priority < inference.priority) {
2421724213
inference.candidates = undefined;
@@ -24240,15 +24236,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2424024236
}
2424124237
}
2424224238
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-
// }
2425224239
return;
2425324240
}
2425424241
// 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 {
2481324800

2481424801
/**
2481524802
* 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.
2481724808
*/
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;
2482024811
// We want to keep a flat list of alternatives in the top-level inference context;
2482124812
// so we perform our new forking action for each existing alternative, and then when
2482224813
// we're done making all the new forks, we'll replace the existing alternative list
2482324814
// with the new one.
24824-
(oldContext.alternatives || [oldContext]).forEach(alternative => {
24815+
flattenInferenceContextAlternatives(context).forEach(alternative => {
2482524816
// It's intentional that the `clone` here doesn't clone the `alternatives` - we want this
2482624817
// fork to be ignorant to the other forks we'll be trying - only the outermost context
2482724818
// 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;
2483024820
context = newContext;
2483124821
action();
2483224822
context = oldContext;
@@ -24843,8 +24833,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2484324833
// Only one alternative was tried - inline it as the new outer context.
2484424834
mergeInferenceContexts(context, group[0]);
2484524835
context.freeTypeVariables = group[0].freeTypeVariables;
24836+
context.freeTypeVariableSourceSignatures = group[0].freeTypeVariableSourceSignatures;
2484624837
context.inferredTypeParameters = group[0].inferredTypeParameters;
2484724838
context.flags = group[0].flags;
24839+
context.alternatives = undefined;
2484824840
return;
2484924841
}
2485024842
context.alternatives = group;
@@ -24885,6 +24877,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2488524877
// inference from a set of overloads to a single signature - produce matches for _every_ overload
2488624878
const group = forkInferenceContext();
2488724879
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+
}
2488824883
spawnAlternativeInferenceContext(group, () =>
2488924884
inferFromSignature(sourceSignatures[i], targetSignatures[0])
2489024885
);
@@ -24904,17 +24899,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2490424899
function inferFromSignature(source: Signature, target: Signature) {
2490524900
target = getErasedSignature(target);
2490624901
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
2491024916
}
2491124917
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;
2491724919
source = instantiateSignature(source, makeUnaryTypeMapper(unknownType, unknownType));
24920+
cache.set(originalSignature, source);
2491824921
(context.freeTypeVariables ||= []).push(...map(source.typeParameters!, createInferenceInfo));
2491924922
}
2492024923
}

src/compiler/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6683,8 +6683,6 @@ export interface Signature {
66836683
/** @internal */
66846684
canonicalSignatureCache?: Signature; // Canonical version of signature (deferred)
66856685
/** @internal */
6686-
baseSignatureCache?: Signature; // Base version of signature (deferred)
6687-
/** @internal */
66886686
optionalCallSignatureCache?: { inner?: Signature, outer?: Signature }; // Optional chained call version of signature (deferred)
66896687
/** @internal */
66906688
isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison
@@ -6793,7 +6791,8 @@ export interface InferenceContext {
67936791
inferredTypeParameters?: readonly TypeParameter[]; // Inferred type parameters for function result
67946792
intraExpressionInferenceSites?: IntraExpressionInferenceSite[];
67956793
freeTypeVariables?: InferenceInfo[]; // Extra inferences made for type parameters found during inference
6796-
alternatives?: InferenceContext[];
6794+
freeTypeVariableSourceSignatures?: Map<object, Map<Signature, Signature>>; // For each recusion identity on the source stack, a cached mapping of signature copies made
6795+
alternatives?: InferenceContext[]
67976796
}
67986797

67996798
/** @internal */

tests/baselines/reference/flatArrayNoExcessiveStackDepth.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ tests/cases/compiler/flatArrayNoExcessiveStackDepth.ts(20,5): error TS2322: Type
55
Type 'unknown' is not assignable to type 'Arr extends readonly (infer InnerArr)[] ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]> : Arr'.
66
Type 'FlatArray<InnerArr, 0 | 2 | 1 | -1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20>' is not assignable to type 'FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]>'.
77
Type 'InnerArr' is not assignable to type 'FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]>'.
8-
Type 'InnerArr' is not assignable to type '(InnerArr extends readonly (infer InnerArr)[] ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]]> : InnerArr) & InnerArr'.
8+
Type 'InnerArr' is not assignable to type 'InnerArr & (InnerArr extends readonly (infer InnerArr)[] ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]]> : InnerArr)'.
99
Type 'InnerArr' is not assignable to type 'InnerArr extends readonly (infer InnerArr)[] ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]]> : InnerArr'.
1010

1111

@@ -38,7 +38,7 @@ tests/cases/compiler/flatArrayNoExcessiveStackDepth.ts(20,5): error TS2322: Type
3838
!!! error TS2322: Type 'unknown' is not assignable to type 'Arr extends readonly (infer InnerArr)[] ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]> : Arr'.
3939
!!! error TS2322: Type 'FlatArray<InnerArr, 0 | 2 | 1 | -1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20>' is not assignable to type 'FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]>'.
4040
!!! error TS2322: Type 'InnerArr' is not assignable to type 'FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]>'.
41-
!!! error TS2322: Type 'InnerArr' is not assignable to type '(InnerArr extends readonly (infer InnerArr)[] ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]]> : InnerArr) & InnerArr'.
41+
!!! error TS2322: Type 'InnerArr' is not assignable to type 'InnerArr & (InnerArr extends readonly (infer InnerArr)[] ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]]> : InnerArr)'.
4242
!!! error TS2322: Type 'InnerArr' is not assignable to type 'InnerArr extends readonly (infer InnerArr)[] ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][D]]> : InnerArr'.
4343
}
4444

0 commit comments

Comments
 (0)
0