Skip to content

Commit

Permalink
Add 'T | PromiseLike<T>' inference from awaited types
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Mar 26, 2020
1 parent 9119fe3 commit 17517d5
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 7 deletions.
46 changes: 39 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18441,10 +18441,45 @@ namespace ts {
return typeVariable;
}

function isPromiseForType(promiseType: Type, promisedType: Type) {
return getPromisedTypeOfPromise(promiseType) === promisedType;
}

function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) {
let typeVariableCount = 0;
if (targetFlags & TypeFlags.Union) {
let nakedTypeVariable: Type | undefined;
for (const t of targets) {
if (getInferenceInfoForType(t)) {
nakedTypeVariable = t;
typeVariableCount++;
}
}

// 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`).
if (typeVariableCount === 1) {
let remainderArePromises = false;
for (const t of targets) {
if (!getInferenceInfoForType(t)) {
if (isPromiseForType(t, nakedTypeVariable!)) {
remainderArePromises = true;
}
else {
remainderArePromises = false;
break;
}
}
}
// 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;
Expand All @@ -18453,11 +18488,7 @@ namespace ts {
// equal priority (i.e. of equal quality) to what we would infer for a naked type
// parameter.
for (const t of targets) {
if (getInferenceInfoForType(t)) {
nakedTypeVariable = t;
typeVariableCount++;
}
else {
if (!getInferenceInfoForType(t)) {
for (let i = 0; i < sources.length; i++) {
const saveInferencePriority = inferencePriority;
inferencePriority = InferencePriority.MaxValue;
Expand Down Expand Up @@ -30128,11 +30159,12 @@ namespace ts {
return typeAsPromise.promisedTypeOfPromise;
}

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

const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
if (isTypeAny(thenFunction)) {
return undefined;
}
Expand Down
67 changes: 67 additions & 0 deletions tests/baselines/reference/promiseTypeInference2.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
=== tests/cases/compiler/promiseTypeInference2.ts ===
const p1 = Promise.resolve(null);
>p1 : Symbol(p1, Decl(promiseTypeInference2.ts, 0, 5))
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))

const p2 = p1.then(() => 100);
>p2 : Symbol(p2, Decl(promiseTypeInference2.ts, 1, 5))
>p1.then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
>p1 : Symbol(p1, Decl(promiseTypeInference2.ts, 0, 5))
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))

const p3 = p1.then(() => Promise.resolve(100));
>p3 : Symbol(p3, Decl(promiseTypeInference2.ts, 2, 5))
>p1.then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
>p1 : Symbol(p1, Decl(promiseTypeInference2.ts, 0, 5))
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))

declare const p4: Promise<number> | Promise<string>;
>p4 : Symbol(p4, Decl(promiseTypeInference2.ts, 4, 13))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))

const p5 = Promise.resolve(p4);
>p5 : Symbol(p5, Decl(promiseTypeInference2.ts, 5, 5))
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>p4 : Symbol(p4, Decl(promiseTypeInference2.ts, 4, 13))

declare const p6: PromiseLike<number> & { x: 1 } | PromiseLike<string> & { x: 2 };
>p6 : Symbol(p6, Decl(promiseTypeInference2.ts, 7, 13))
>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(promiseTypeInference2.ts, 7, 41))
>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(promiseTypeInference2.ts, 7, 74))

const p7 = Promise.resolve(p6);
>p7 : Symbol(p7, Decl(promiseTypeInference2.ts, 8, 5))
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>p6 : Symbol(p6, Decl(promiseTypeInference2.ts, 7, 13))

declare function resolve<T>(value: T | PromiseLike<T> & { x: 1 } | PromiseLike<T> & { x: 2 }): Promise<T>;
>resolve : Symbol(resolve, Decl(promiseTypeInference2.ts, 8, 31))
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))
>value : Symbol(value, Decl(promiseTypeInference2.ts, 10, 28))
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))
>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))
>x : Symbol(x, Decl(promiseTypeInference2.ts, 10, 57))
>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))
>x : Symbol(x, Decl(promiseTypeInference2.ts, 10, 85))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))

const p8 = resolve(p6);
>p8 : Symbol(p8, Decl(promiseTypeInference2.ts, 11, 5))
>resolve : Symbol(resolve, Decl(promiseTypeInference2.ts, 8, 31))
>p6 : Symbol(p6, Decl(promiseTypeInference2.ts, 7, 13))

67 changes: 67 additions & 0 deletions tests/baselines/reference/promiseTypeInference2.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
=== tests/cases/compiler/promiseTypeInference2.ts ===
const p1 = Promise.resolve(null);
>p1 : Promise<any>
>Promise.resolve(null) : Promise<any>
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>Promise : PromiseConstructor
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>null : null

const p2 = p1.then(() => 100);
>p2 : Promise<number>
>p1.then(() => 100) : Promise<number>
>p1.then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>p1 : Promise<any>
>then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>() => 100 : () => number
>100 : 100

const p3 = p1.then(() => Promise.resolve(100));
>p3 : Promise<number>
>p1.then(() => Promise.resolve(100)) : Promise<number>
>p1.then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>p1 : Promise<any>
>then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>() => Promise.resolve(100) : () => Promise<number>
>Promise.resolve(100) : Promise<number>
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>Promise : PromiseConstructor
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>100 : 100

declare const p4: Promise<number> | Promise<string>;
>p4 : Promise<number> | Promise<string>

const p5 = Promise.resolve(p4);
>p5 : Promise<string | number>
>Promise.resolve(p4) : Promise<string | number>
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>Promise : PromiseConstructor
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>p4 : Promise<number> | Promise<string>

declare const p6: PromiseLike<number> & { x: 1 } | PromiseLike<string> & { x: 2 };
>p6 : (PromiseLike<number> & { x: 1; }) | (PromiseLike<string> & { x: 2; })
>x : 1
>x : 2

const p7 = Promise.resolve(p6);
>p7 : Promise<string | number>
>Promise.resolve(p6) : Promise<string | number>
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>Promise : PromiseConstructor
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
>p6 : (PromiseLike<number> & { x: 1; }) | (PromiseLike<string> & { x: 2; })

declare function resolve<T>(value: T | PromiseLike<T> & { x: 1 } | PromiseLike<T> & { x: 2 }): Promise<T>;
>resolve : <T>(value: T | (PromiseLike<T> & { x: 1; }) | (PromiseLike<T> & { x: 2; })) => Promise<T>
>value : T | (PromiseLike<T> & { x: 1; }) | (PromiseLike<T> & { x: 2; })
>x : 1
>x : 2

const p8 = resolve(p6);
>p8 : Promise<string | number>
>resolve(p6) : Promise<string | number>
>resolve : <T>(value: T | (PromiseLike<T> & { x: 1; }) | (PromiseLike<T> & { x: 2; })) => Promise<T>
>p6 : (PromiseLike<number> & { x: 1; }) | (PromiseLike<string> & { x: 2; })

15 changes: 15 additions & 0 deletions tests/cases/compiler/promiseTypeInference2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @target: es2015
// @noEmit: true

const p1 = Promise.resolve(null);
const p2 = p1.then(() => 100);
const p3 = p1.then(() => Promise.resolve(100));

declare const p4: Promise<number> | Promise<string>;
const p5 = Promise.resolve(p4);

declare const p6: PromiseLike<number> & { x: 1 } | PromiseLike<string> & { x: 2 };
const p7 = Promise.resolve(p6);

declare function resolve<T>(value: T | PromiseLike<T> & { x: 1 } | PromiseLike<T> & { x: 2 }): Promise<T>;
const p8 = resolve(p6);

0 comments on commit 17517d5

Please sign in to comment.