Skip to content

Commit 3d2ac60

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

File tree

6 files changed

+1886
-123
lines changed

6 files changed

+1886
-123
lines changed

src/compiler/checker.ts

+31-17
Original file line numberDiff line numberDiff line change
@@ -18462,6 +18462,8 @@ namespace ts {
1846218462
let typeVariableCount = 0;
1846318463
if (targetFlags & TypeFlags.Union) {
1846418464
let nakedTypeVariable: Type | undefined;
18465+
let remainderArePromises = false;
18466+
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
1846518467
for (const t of targets) {
1846618468
if (getInferenceInfoForType(t)) {
1846718469
nakedTypeVariable = t;
@@ -18470,11 +18472,13 @@ namespace ts {
1847018472
}
1847118473

1847218474
// 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`).
18475+
// (for any compatible `PromiseLike`). When encountered, we infer all promise-like sources to the promise-like
18476+
// targets and all non-promise-like sources to the naked type variable. For example when inferring to
18477+
// `T | PromiseLike<T>` from `number | boolean | Promise<string>`, we infer to `PromiseLike<T>` from
18478+
// `Promise<string>` and to `T` for `number | boolean`. We allow for multiple promise-like types in target
18479+
// to better support JQuery's `PromiseBase` which returns something like
18480+
// `PromiseBase<T, U, V...> | PromiseLike<T> | T` in its fulfillment callbacks.
1847618481
if (typeVariableCount === 1) {
18477-
let remainderArePromises = false;
1847818482
for (const t of targets) {
1847918483
if (!getInferenceInfoForType(t)) {
1848018484
if (isPromiseForType(t, nakedTypeVariable!)) {
@@ -18486,14 +18490,8 @@ namespace ts {
1848618490
}
1848718491
}
1848818492
}
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-
}
1849418493
}
1849518494

18496-
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
1849718495
const matched = new Array<boolean>(sources.length);
1849818496
let inferenceCircularity = false;
1849918497
// First infer to types that are not naked type variables. For each source type we
@@ -18503,12 +18501,28 @@ namespace ts {
1850318501
for (const t of targets) {
1850418502
if (!getInferenceInfoForType(t)) {
1850518503
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);
18504+
// When inferring to a union consisting solely `T` and promise-like constituents,
18505+
// we infer only promise-like sources to promise-like constituents.
18506+
let promisedType: Type | undefined;
18507+
if (!remainderArePromises || (promisedType = getPromisedTypeOfPromise(sources[i]))) {
18508+
const saveInferencePriority = inferencePriority;
18509+
inferencePriority = InferencePriority.MaxValue;
18510+
// When inferring to a union of `T | PromiseLike<T>`, we will first
18511+
// infer a promise-like source to a promise-like target, but we will
18512+
// also infer the promised type directly to the type variable.
18513+
inferFromTypes(sources[i], t);
18514+
if (inferencePriority === priority) {
18515+
if (promisedType) {
18516+
sources[i] = promisedType;
18517+
matched[i] = false;
18518+
}
18519+
else {
18520+
matched[i] = true;
18521+
}
18522+
}
18523+
inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity;
18524+
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
18525+
}
1851218526
}
1851318527
}
1851418528
}
@@ -18527,7 +18541,7 @@ 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) => matched[i] ? undefined : s);
1853118545
if (unmatched.length) {
1853218546
inferFromTypes(getUnionType(unmatched), nakedTypeVariable!);
1853318547
return;

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)