Skip to content

Commit

Permalink
Ensure inferences to other type parameters are made
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Apr 7, 2020
1 parent 3b60fe0 commit 24b6d64
Show file tree
Hide file tree
Showing 6 changed files with 1,895 additions and 125 deletions.
59 changes: 40 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18458,10 +18458,16 @@ namespace ts {
return getPromisedTypeOfPromise(promiseType) === promisedType;
}

function isUserDefinedPromiseType(type: Type) {
return !isReferenceToGlobalPromiseType(type) && !!getPromisedTypeOfPromise(type);
}

function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) {
let typeVariableCount = 0;
if (targetFlags & TypeFlags.Union) {
let nakedTypeVariable: Type | undefined;
let remainderArePromises = false;
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
for (const t of targets) {
if (getInferenceInfoForType(t)) {
nakedTypeVariable = t;
Expand All @@ -18470,11 +18476,13 @@ namespace ts {
}

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

const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
const matched = new Array<boolean>(sources.length);
let inferenceCircularity = false;
// First infer to types that are not naked type variables. For each source type we
Expand All @@ -18503,12 +18505,24 @@ namespace ts {
for (const t of targets) {
if (!getInferenceInfoForType(t)) {
for (let i = 0; i < sources.length; i++) {
const saveInferencePriority = inferencePriority;
inferencePriority = InferencePriority.MaxValue;
inferFromTypes(sources[i], t);
if (inferencePriority === priority) matched[i] = true;
inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity;
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
// When inferring to a union consisting solely of `T` and promise-like constituents,
// we infer only custom promise-like sources to promise-like constituents so that we can
// capture inferences to other type arguments. We skip sources with references to the
// global `Promise<T>` and `PromiseLike<T>` types as we can infer the promised types
// of those directly to the type variable, below.
if (!remainderArePromises || isUserDefinedPromiseType(sources[i])) {
const saveInferencePriority = inferencePriority;
inferencePriority = InferencePriority.MaxValue;
// When inferring to a union of `T | PromiseLike<T>`, we will first
// infer a promise-like source to a promise-like target, but we will
// also infer the promised type directly to the type variable.
inferFromTypes(sources[i], t);
if (inferencePriority === priority && !remainderArePromises) {
matched[i] = true;
}
inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity;
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
}
}
}
}
Expand All @@ -18527,7 +18541,10 @@ namespace ts {
// types from which no inferences have been made so far and infer from that union to the
// naked type variable.
if (typeVariableCount === 1 && !inferenceCircularity) {
const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s);
const unmatched = mapDefined(sources, (s, i) =>
matched[i] ? undefined :
remainderArePromises ? getPromisedTypeOfPromise(s) ?? s :
s);
if (unmatched.length) {
inferFromTypes(getUnionType(unmatched), nakedTypeVariable!);
return;
Expand Down Expand Up @@ -30147,6 +30164,11 @@ namespace ts {
return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
}

function isReferenceToGlobalPromiseType(type: Type) {
return isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))
|| isReferenceToType(type, getGlobalPromiseLikeType(/*reportErrors*/ false));
}

/**
* Gets the "promised type" of a promise.
* @param type The type of the promise.
Expand All @@ -30172,8 +30194,7 @@ namespace ts {
return typeAsPromise.promisedTypeOfPromise;
}

if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false)) ||
isReferenceToType(type, getGlobalPromiseLikeType(/*reportErrors*/ false))) {
if (isReferenceToGlobalPromiseType(type)) {
return typeAsPromise.promisedTypeOfPromise = getTypeArguments(<GenericType>type)[0];
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/es2015.promise.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ interface PromiseConstructor {

/**
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @param value A promise or a non-promise value.
* @returns A promise whose internal state matches the provided promise.
*/
resolve<T>(value: T | PromiseLike<T>): Promise<T>;
Expand Down
17 changes: 11 additions & 6 deletions src/services/documentRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,18 @@ namespace ts {
}

function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void {
const bucket = Debug.checkDefined(buckets.get(key));
const entry = bucket.get(path)!;
entry.languageServiceRefCount--;
try {
const bucket = Debug.checkDefined(buckets.get(key));
const entry = bucket.get(path)!;
entry.languageServiceRefCount--;

Debug.assert(entry.languageServiceRefCount >= 0);
if (entry.languageServiceRefCount === 0) {
bucket.delete(path);
Debug.assert(entry.languageServiceRefCount >= 0);
if (entry.languageServiceRefCount === 0) {
bucket.delete(path);
}
}
catch (e) {
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())])}`);
}
}

Expand Down
Loading

0 comments on commit 24b6d64

Please sign in to comment.