From 17517d5085503d097244cb6fee59c2e3c715fcef Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 25 Mar 2020 20:06:20 -0700 Subject: [PATCH] Add 'T | PromiseLike' inference from awaited types --- src/compiler/checker.ts | 46 +++++++++++-- .../reference/promiseTypeInference2.symbols | 67 +++++++++++++++++++ .../reference/promiseTypeInference2.types | 67 +++++++++++++++++++ tests/cases/compiler/promiseTypeInference2.ts | 15 +++++ 4 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/promiseTypeInference2.symbols create mode 100644 tests/baselines/reference/promiseTypeInference2.types create mode 100644 tests/cases/compiler/promiseTypeInference2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e0672300f1c..d9213052aba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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` + // (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` 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 ? (source).types : [source]; const matched = new Array(sources.length); let inferenceCircularity = false; @@ -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; @@ -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(type)[0]; } - const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String); if (isTypeAny(thenFunction)) { return undefined; } diff --git a/tests/baselines/reference/promiseTypeInference2.symbols b/tests/baselines/reference/promiseTypeInference2.symbols new file mode 100644 index 00000000000..310ef957e2d --- /dev/null +++ b/tests/baselines/reference/promiseTypeInference2.symbols @@ -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 | Promise; +>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 & { x: 1 } | PromiseLike & { 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(value: T | PromiseLike & { x: 1 } | PromiseLike & { x: 2 }): Promise; +>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)) + diff --git a/tests/baselines/reference/promiseTypeInference2.types b/tests/baselines/reference/promiseTypeInference2.types new file mode 100644 index 00000000000..488d9ff4b43 --- /dev/null +++ b/tests/baselines/reference/promiseTypeInference2.types @@ -0,0 +1,67 @@ +=== tests/cases/compiler/promiseTypeInference2.ts === +const p1 = Promise.resolve(null); +>p1 : Promise +>Promise.resolve(null) : Promise +>Promise.resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>Promise : PromiseConstructor +>resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>null : null + +const p2 = p1.then(() => 100); +>p2 : Promise +>p1.then(() => 100) : Promise +>p1.then : (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>p1 : Promise +>then : (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>() => 100 : () => number +>100 : 100 + +const p3 = p1.then(() => Promise.resolve(100)); +>p3 : Promise +>p1.then(() => Promise.resolve(100)) : Promise +>p1.then : (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>p1 : Promise +>then : (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>() => Promise.resolve(100) : () => Promise +>Promise.resolve(100) : Promise +>Promise.resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>Promise : PromiseConstructor +>resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>100 : 100 + +declare const p4: Promise | Promise; +>p4 : Promise | Promise + +const p5 = Promise.resolve(p4); +>p5 : Promise +>Promise.resolve(p4) : Promise +>Promise.resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>Promise : PromiseConstructor +>resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>p4 : Promise | Promise + +declare const p6: PromiseLike & { x: 1 } | PromiseLike & { x: 2 }; +>p6 : (PromiseLike & { x: 1; }) | (PromiseLike & { x: 2; }) +>x : 1 +>x : 2 + +const p7 = Promise.resolve(p6); +>p7 : Promise +>Promise.resolve(p6) : Promise +>Promise.resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>Promise : PromiseConstructor +>resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>p6 : (PromiseLike & { x: 1; }) | (PromiseLike & { x: 2; }) + +declare function resolve(value: T | PromiseLike & { x: 1 } | PromiseLike & { x: 2 }): Promise; +>resolve : (value: T | (PromiseLike & { x: 1; }) | (PromiseLike & { x: 2; })) => Promise +>value : T | (PromiseLike & { x: 1; }) | (PromiseLike & { x: 2; }) +>x : 1 +>x : 2 + +const p8 = resolve(p6); +>p8 : Promise +>resolve(p6) : Promise +>resolve : (value: T | (PromiseLike & { x: 1; }) | (PromiseLike & { x: 2; })) => Promise +>p6 : (PromiseLike & { x: 1; }) | (PromiseLike & { x: 2; }) + diff --git a/tests/cases/compiler/promiseTypeInference2.ts b/tests/cases/compiler/promiseTypeInference2.ts new file mode 100644 index 00000000000..3be1a348d42 --- /dev/null +++ b/tests/cases/compiler/promiseTypeInference2.ts @@ -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 | Promise; +const p5 = Promise.resolve(p4); + +declare const p6: PromiseLike & { x: 1 } | PromiseLike & { x: 2 }; +const p7 = Promise.resolve(p6); + +declare function resolve(value: T | PromiseLike & { x: 1 } | PromiseLike & { x: 2 }): Promise; +const p8 = resolve(p6);