Skip to content

Commit 24b6d64

Browse files
committed
Ensure inferences to other type parameters are made
1 parent 3b60fe0 commit 24b6d64

File tree

6 files changed

+1895
-125
lines changed

6 files changed

+1895
-125
lines changed

src/compiler/checker.ts

+40-19
Original file line numberDiff line numberDiff line change
@@ -18458,10 +18458,16 @@ namespace ts {
1845818458
return getPromisedTypeOfPromise(promiseType) === promisedType;
1845918459
}
1846018460

18461+
function isUserDefinedPromiseType(type: Type) {
18462+
return !isReferenceToGlobalPromiseType(type) && !!getPromisedTypeOfPromise(type);
18463+
}
18464+
1846118465
function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) {
1846218466
let typeVariableCount = 0;
1846318467
if (targetFlags & TypeFlags.Union) {
1846418468
let nakedTypeVariable: Type | undefined;
18469+
let remainderArePromises = false;
18470+
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
1846518471
for (const t of targets) {
1846618472
if (getInferenceInfoForType(t)) {
1846718473
nakedTypeVariable = t;
@@ -18470,11 +18476,13 @@ namespace ts {
1847018476
}
1847118477

1847218478
// To better handle inference for `Promise`-like types, we detect a target union of `T | PromiseLike<T>`
18473-
// (for any compatible `PromiseLike`). When encountered, we infer from source to the type parameter `T`,
18474-
// where each type of source is mapped to extract the promised type of any promise (e.g.,
18475-
// `string | Promise<number>` becomes `string | number`).
18479+
// (for any compatible `PromiseLike`). When encountered, we infer all promise-like sources to the promise-like
18480+
// targets and all non-promise-like sources to the naked type variable. For example when inferring to
18481+
// `T | PromiseLike<T>` from `number | boolean | Promise<string>`, we infer to `PromiseLike<T>` from
18482+
// `Promise<string>` and to `T` for `number | boolean`. We allow for multiple promise-like types in target
18483+
// to better support JQuery's `PromiseBase` which returns something like
18484+
// `PromiseBase<T, U, V...> | PromiseLike<T> | T` in its fulfillment callbacks.
1847618485
if (typeVariableCount === 1) {
18477-
let remainderArePromises = false;
1847818486
for (const t of targets) {
1847918487
if (!getInferenceInfoForType(t)) {
1848018488
if (isPromiseForType(t, nakedTypeVariable!)) {
@@ -18486,14 +18494,8 @@ namespace ts {
1848618494
}
1848718495
}
1848818496
}
18489-
// remaining constituents are promise-like types whose "promised types" are `T`
18490-
if (remainderArePromises) {
18491-
inferFromTypes(mapType(source, s => getPromisedTypeOfPromise(s) ?? s), nakedTypeVariable!);
18492-
return;
18493-
}
1849418497
}
1849518498

18496-
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
1849718499
const matched = new Array<boolean>(sources.length);
1849818500
let inferenceCircularity = false;
1849918501
// First infer to types that are not naked type variables. For each source type we
@@ -18503,12 +18505,24 @@ namespace ts {
1850318505
for (const t of targets) {
1850418506
if (!getInferenceInfoForType(t)) {
1850518507
for (let i = 0; i < sources.length; i++) {
18506-
const saveInferencePriority = inferencePriority;
18507-
inferencePriority = InferencePriority.MaxValue;
18508-
inferFromTypes(sources[i], t);
18509-
if (inferencePriority === priority) matched[i] = true;
18510-
inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity;
18511-
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
18508+
// When inferring to a union consisting solely of `T` and promise-like constituents,
18509+
// we infer only custom promise-like sources to promise-like constituents so that we can
18510+
// capture inferences to other type arguments. We skip sources with references to the
18511+
// global `Promise<T>` and `PromiseLike<T>` types as we can infer the promised types
18512+
// of those directly to the type variable, below.
18513+
if (!remainderArePromises || isUserDefinedPromiseType(sources[i])) {
18514+
const saveInferencePriority = inferencePriority;
18515+
inferencePriority = InferencePriority.MaxValue;
18516+
// When inferring to a union of `T | PromiseLike<T>`, we will first
18517+
// infer a promise-like source to a promise-like target, but we will
18518+
// also infer the promised type directly to the type variable.
18519+
inferFromTypes(sources[i], t);
18520+
if (inferencePriority === priority && !remainderArePromises) {
18521+
matched[i] = true;
18522+
}
18523+
inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity;
18524+
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
18525+
}
1851218526
}
1851318527
}
1851418528
}
@@ -18527,7 +18541,10 @@ namespace ts {
1852718541
// types from which no inferences have been made so far and infer from that union to the
1852818542
// naked type variable.
1852918543
if (typeVariableCount === 1 && !inferenceCircularity) {
18530-
const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s);
18544+
const unmatched = mapDefined(sources, (s, i) =>
18545+
matched[i] ? undefined :
18546+
remainderArePromises ? getPromisedTypeOfPromise(s) ?? s :
18547+
s);
1853118548
if (unmatched.length) {
1853218549
inferFromTypes(getUnionType(unmatched), nakedTypeVariable!);
1853318550
return;
@@ -30147,6 +30164,11 @@ namespace ts {
3014730164
return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
3014830165
}
3014930166

30167+
function isReferenceToGlobalPromiseType(type: Type) {
30168+
return isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))
30169+
|| isReferenceToType(type, getGlobalPromiseLikeType(/*reportErrors*/ false));
30170+
}
30171+
3015030172
/**
3015130173
* Gets the "promised type" of a promise.
3015230174
* @param type The type of the promise.
@@ -30172,8 +30194,7 @@ namespace ts {
3017230194
return typeAsPromise.promisedTypeOfPromise;
3017330195
}
3017430196

30175-
if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false)) ||
30176-
isReferenceToType(type, getGlobalPromiseLikeType(/*reportErrors*/ false))) {
30197+
if (isReferenceToGlobalPromiseType(type)) {
3017730198
return typeAsPromise.promisedTypeOfPromise = getTypeArguments(<GenericType>type)[0];
3017830199
}
3017930200

src/lib/es2015.promise.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ interface PromiseConstructor {
115115

116116
/**
117117
* Creates a new resolved promise for the provided value.
118-
* @param value A promise.
118+
* @param value A promise or a non-promise value.
119119
* @returns A promise whose internal state matches the provided promise.
120120
*/
121121
resolve<T>(value: T | PromiseLike<T>): Promise<T>;

src/services/documentRegistry.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,18 @@ namespace ts {
230230
}
231231

232232
function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void {
233-
const bucket = Debug.checkDefined(buckets.get(key));
234-
const entry = bucket.get(path)!;
235-
entry.languageServiceRefCount--;
233+
try {
234+
const bucket = Debug.checkDefined(buckets.get(key));
235+
const entry = bucket.get(path)!;
236+
entry.languageServiceRefCount--;
236237

237-
Debug.assert(entry.languageServiceRefCount >= 0);
238-
if (entry.languageServiceRefCount === 0) {
239-
bucket.delete(path);
238+
Debug.assert(entry.languageServiceRefCount >= 0);
239+
if (entry.languageServiceRefCount === 0) {
240+
bucket.delete(path);
241+
}
242+
}
243+
catch (e) {
244+
throw new Error(`${e.message}\npath: ${path}\nkey: ${key}\nhas key: ${buckets.has(key)}\nhas bucket: ${!!buckets.get(key)}\nhas entry: ${buckets.get(key)?.has(path)}\npaths in bucket: ${JSON.stringify([...arrayFrom((buckets.get(key) ?? createMap()).keys())])}`);
240245
}
241246
}
242247

0 commit comments

Comments
 (0)