Skip to content

Commit 39eb820

Browse files
committed
Add 'T | PromiseLike<T>' inference from awaited types
1 parent 9119fe3 commit 39eb820

File tree

4 files changed

+187
-7
lines changed

4 files changed

+187
-7
lines changed

src/compiler/checker.ts

+38-7
Original file line numberDiff line numberDiff line change
@@ -18441,10 +18441,44 @@ namespace ts {
1844118441
return typeVariable;
1844218442
}
1844318443

18444+
function isPromiseForType(promiseType: Type, promisedType: Type) {
18445+
return getPromisedTypeOfPromise(promiseType) === promisedType;
18446+
}
18447+
1844418448
function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) {
1844518449
let typeVariableCount = 0;
1844618450
if (targetFlags & TypeFlags.Union) {
1844718451
let nakedTypeVariable: Type | undefined;
18452+
for (const t of targets) {
18453+
if (getInferenceInfoForType(t)) {
18454+
nakedTypeVariable = t;
18455+
typeVariableCount++;
18456+
}
18457+
}
18458+
18459+
// To better handle inference for `Promise`-like types, we detect a target union of `T | PromiseLike<T>`
18460+
// (for any compatible `PromiseLike`). When encountered, we infer from source to the type parameter `T`,
18461+
// where each type of source is mapped to extract the promised type of any promise (e.g.,
18462+
// `string | Promise<number>` becomes `string | number`).
18463+
if (typeVariableCount === 1) {
18464+
let remainderArePromises = false;
18465+
for (const t of targets) {
18466+
if (!getInferenceInfoForType(t)) {
18467+
if (isPromiseForType(t, nakedTypeVariable!)) {
18468+
remainderArePromises = true;
18469+
} else {
18470+
remainderArePromises = false;
18471+
break;
18472+
}
18473+
}
18474+
}
18475+
// remaining constituents are promise-like types whose "promised types" are `T`
18476+
if (remainderArePromises) {
18477+
inferFromTypes(mapType(source, s => getPromisedTypeOfPromise(s) ?? s), nakedTypeVariable!);
18478+
return;
18479+
}
18480+
}
18481+
1844818482
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
1844918483
const matched = new Array<boolean>(sources.length);
1845018484
let inferenceCircularity = false;
@@ -18453,11 +18487,7 @@ namespace ts {
1845318487
// equal priority (i.e. of equal quality) to what we would infer for a naked type
1845418488
// parameter.
1845518489
for (const t of targets) {
18456-
if (getInferenceInfoForType(t)) {
18457-
nakedTypeVariable = t;
18458-
typeVariableCount++;
18459-
}
18460-
else {
18490+
if (!getInferenceInfoForType(t)) {
1846118491
for (let i = 0; i < sources.length; i++) {
1846218492
const saveInferencePriority = inferencePriority;
1846318493
inferencePriority = InferencePriority.MaxValue;
@@ -30128,11 +30158,12 @@ namespace ts {
3012830158
return typeAsPromise.promisedTypeOfPromise;
3012930159
}
3013030160

30131-
if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) {
30161+
if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false)) ||
30162+
isReferenceToType(type, getGlobalPromiseLikeType(/*reportErrors*/ false))) {
3013230163
return typeAsPromise.promisedTypeOfPromise = getTypeArguments(<GenericType>type)[0];
3013330164
}
3013430165

30135-
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217
30166+
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
3013630167
if (isTypeAny(thenFunction)) {
3013730168
return undefined;
3013830169
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
=== tests/cases/compiler/promiseTypeInference2.ts ===
2+
const p1 = Promise.resolve(null);
3+
>p1 : Symbol(p1, Decl(promiseTypeInference2.ts, 0, 5))
4+
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
5+
>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, --, --))
6+
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
7+
8+
const p2 = p1.then(() => 100);
9+
>p2 : Symbol(p2, Decl(promiseTypeInference2.ts, 1, 5))
10+
>p1.then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
11+
>p1 : Symbol(p1, Decl(promiseTypeInference2.ts, 0, 5))
12+
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
13+
14+
const p3 = p1.then(() => Promise.resolve(100));
15+
>p3 : Symbol(p3, Decl(promiseTypeInference2.ts, 2, 5))
16+
>p1.then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
17+
>p1 : Symbol(p1, Decl(promiseTypeInference2.ts, 0, 5))
18+
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
19+
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
20+
>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, --, --))
21+
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
22+
23+
declare const p4: Promise<number> | Promise<string>;
24+
>p4 : Symbol(p4, Decl(promiseTypeInference2.ts, 4, 13))
25+
>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, --, --))
26+
>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, --, --))
27+
28+
const p5 = Promise.resolve(p4);
29+
>p5 : Symbol(p5, Decl(promiseTypeInference2.ts, 5, 5))
30+
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
31+
>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, --, --))
32+
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
33+
>p4 : Symbol(p4, Decl(promiseTypeInference2.ts, 4, 13))
34+
35+
declare const p6: PromiseLike<number> & { x: 1 } | PromiseLike<string> & { x: 2 };
36+
>p6 : Symbol(p6, Decl(promiseTypeInference2.ts, 7, 13))
37+
>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --))
38+
>x : Symbol(x, Decl(promiseTypeInference2.ts, 7, 41))
39+
>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --))
40+
>x : Symbol(x, Decl(promiseTypeInference2.ts, 7, 74))
41+
42+
const p7 = Promise.resolve(p6);
43+
>p7 : Symbol(p7, Decl(promiseTypeInference2.ts, 8, 5))
44+
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
45+
>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, --, --))
46+
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
47+
>p6 : Symbol(p6, Decl(promiseTypeInference2.ts, 7, 13))
48+
49+
declare function resolve<T>(value: T | PromiseLike<T> & { x: 1 } | PromiseLike<T> & { x: 2 }): Promise<T>;
50+
>resolve : Symbol(resolve, Decl(promiseTypeInference2.ts, 8, 31))
51+
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))
52+
>value : Symbol(value, Decl(promiseTypeInference2.ts, 10, 28))
53+
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))
54+
>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --))
55+
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))
56+
>x : Symbol(x, Decl(promiseTypeInference2.ts, 10, 57))
57+
>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --))
58+
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))
59+
>x : Symbol(x, Decl(promiseTypeInference2.ts, 10, 85))
60+
>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, --, --))
61+
>T : Symbol(T, Decl(promiseTypeInference2.ts, 10, 25))
62+
63+
const p8 = resolve(p6);
64+
>p8 : Symbol(p8, Decl(promiseTypeInference2.ts, 11, 5))
65+
>resolve : Symbol(resolve, Decl(promiseTypeInference2.ts, 8, 31))
66+
>p6 : Symbol(p6, Decl(promiseTypeInference2.ts, 7, 13))
67+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
=== tests/cases/compiler/promiseTypeInference2.ts ===
2+
const p1 = Promise.resolve(null);
3+
>p1 : Promise<any>
4+
>Promise.resolve(null) : Promise<any>
5+
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
6+
>Promise : PromiseConstructor
7+
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
8+
>null : null
9+
10+
const p2 = p1.then(() => 100);
11+
>p2 : Promise<number>
12+
>p1.then(() => 100) : Promise<number>
13+
>p1.then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
14+
>p1 : Promise<any>
15+
>then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
16+
>() => 100 : () => number
17+
>100 : 100
18+
19+
const p3 = p1.then(() => Promise.resolve(100));
20+
>p3 : Promise<number>
21+
>p1.then(() => Promise.resolve(100)) : Promise<number>
22+
>p1.then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
23+
>p1 : Promise<any>
24+
>then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
25+
>() => Promise.resolve(100) : () => Promise<number>
26+
>Promise.resolve(100) : Promise<number>
27+
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
28+
>Promise : PromiseConstructor
29+
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
30+
>100 : 100
31+
32+
declare const p4: Promise<number> | Promise<string>;
33+
>p4 : Promise<number> | Promise<string>
34+
35+
const p5 = Promise.resolve(p4);
36+
>p5 : Promise<string | number>
37+
>Promise.resolve(p4) : Promise<string | number>
38+
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
39+
>Promise : PromiseConstructor
40+
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
41+
>p4 : Promise<number> | Promise<string>
42+
43+
declare const p6: PromiseLike<number> & { x: 1 } | PromiseLike<string> & { x: 2 };
44+
>p6 : (PromiseLike<number> & { x: 1; }) | (PromiseLike<string> & { x: 2; })
45+
>x : 1
46+
>x : 2
47+
48+
const p7 = Promise.resolve(p6);
49+
>p7 : Promise<string | number>
50+
>Promise.resolve(p6) : Promise<string | number>
51+
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
52+
>Promise : PromiseConstructor
53+
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
54+
>p6 : (PromiseLike<number> & { x: 1; }) | (PromiseLike<string> & { x: 2; })
55+
56+
declare function resolve<T>(value: T | PromiseLike<T> & { x: 1 } | PromiseLike<T> & { x: 2 }): Promise<T>;
57+
>resolve : <T>(value: T | (PromiseLike<T> & { x: 1; }) | (PromiseLike<T> & { x: 2; })) => Promise<T>
58+
>value : T | (PromiseLike<T> & { x: 1; }) | (PromiseLike<T> & { x: 2; })
59+
>x : 1
60+
>x : 2
61+
62+
const p8 = resolve(p6);
63+
>p8 : Promise<string | number>
64+
>resolve(p6) : Promise<string | number>
65+
>resolve : <T>(value: T | (PromiseLike<T> & { x: 1; }) | (PromiseLike<T> & { x: 2; })) => Promise<T>
66+
>p6 : (PromiseLike<number> & { x: 1; }) | (PromiseLike<string> & { x: 2; })
67+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @target: es2015
2+
// @noEmit: true
3+
4+
const p1 = Promise.resolve(null);
5+
const p2 = p1.then(() => 100);
6+
const p3 = p1.then(() => Promise.resolve(100));
7+
8+
declare const p4: Promise<number> | Promise<string>;
9+
const p5 = Promise.resolve(p4);
10+
11+
declare const p6: PromiseLike<number> & { x: 1 } | PromiseLike<string> & { x: 2 };
12+
const p7 = Promise.resolve(p6);
13+
14+
declare function resolve<T>(value: T | PromiseLike<T> & { x: 1 } | PromiseLike<T> & { x: 2 }): Promise<T>;
15+
const p8 = resolve(p6);

0 commit comments

Comments
 (0)