Skip to content

Commit 9e4e569

Browse files
authored
Merge pull request #27270 from Microsoft/fix24570-2
Support promise-like types in contextual return type of async function
2 parents 219bb44 + 3a4d0b2 commit 9e4e569

7 files changed

+248
-4
lines changed

src/compiler/checker.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ namespace ts {
495495
let deferredGlobalESSymbolType: ObjectType;
496496
let deferredGlobalTypedPropertyDescriptorType: GenericType;
497497
let deferredGlobalPromiseType: GenericType;
498+
let deferredGlobalPromiseLikeType: GenericType;
498499
let deferredGlobalPromiseConstructorSymbol: Symbol | undefined;
499500
let deferredGlobalPromiseConstructorLikeType: ObjectType;
500501
let deferredGlobalIterableType: GenericType;
@@ -8538,6 +8539,10 @@ namespace ts {
85388539
return deferredGlobalPromiseType || (deferredGlobalPromiseType = getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
85398540
}
85408541

8542+
function getGlobalPromiseLikeType(reportErrors: boolean) {
8543+
return deferredGlobalPromiseLikeType || (deferredGlobalPromiseLikeType = getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
8544+
}
8545+
85418546
function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined {
85428547
return deferredGlobalPromiseConstructorSymbol || (deferredGlobalPromiseConstructorSymbol = getGlobalValueSymbol("Promise" as __String, reportErrors));
85438548
}
@@ -16393,9 +16398,22 @@ namespace ts {
1639316398
}
1639416399

1639516400
const contextualReturnType = getContextualReturnType(func);
16396-
return functionFlags & FunctionFlags.Async
16397-
? contextualReturnType && getAwaitedTypeOfPromise(contextualReturnType) // Async function
16398-
: contextualReturnType; // Regular function
16401+
if (contextualReturnType) {
16402+
if (functionFlags & FunctionFlags.Async) { // Async function
16403+
const contextualAwaitedType = getAwaitedTypeOfPromise(contextualReturnType);
16404+
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
16405+
}
16406+
return contextualReturnType; // Regular function
16407+
}
16408+
}
16409+
return undefined;
16410+
}
16411+
16412+
function getContextualTypeForAwaitOperand(node: AwaitExpression): Type | undefined {
16413+
const contextualType = getContextualType(node);
16414+
if (contextualType) {
16415+
const contextualAwaitedType = getAwaitedType(contextualType);
16416+
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
1639916417
}
1640016418
return undefined;
1640116419
}
@@ -16761,7 +16779,9 @@ namespace ts {
1676116779
return getContextualTypeForReturnExpression(node);
1676216780
case SyntaxKind.YieldExpression:
1676316781
return getContextualTypeForYieldOperand(<YieldExpression>parent);
16764-
case SyntaxKind.CallExpression:
16782+
case SyntaxKind.AwaitExpression:
16783+
return getContextualTypeForAwaitOperand(<AwaitExpression>parent);
16784+
case SyntaxKind.CallExpression:
1676516785
case SyntaxKind.NewExpression:
1676616786
return getContextualTypeForArgument(<CallExpression | NewExpression>parent, node);
1676716787
case SyntaxKind.TypeAssertionExpression:
@@ -20796,6 +20816,18 @@ namespace ts {
2079620816
return emptyObjectType;
2079720817
}
2079820818

20819+
function createPromiseLikeType(promisedType: Type): Type {
20820+
// creates a `PromiseLike<T>` type where `T` is the promisedType argument
20821+
const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true);
20822+
if (globalPromiseLikeType !== emptyGenericType) {
20823+
// if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type
20824+
promisedType = getAwaitedType(promisedType) || emptyObjectType;
20825+
return createTypeReference(globalPromiseLikeType, [promisedType]);
20826+
}
20827+
20828+
return emptyObjectType;
20829+
}
20830+
2079920831
function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: Type) {
2080020832
const promiseType = createPromiseType(promisedType);
2080120833
if (promiseType === emptyObjectType) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
=== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionAwaitOperand.ts ===
2+
interface Obj { key: "value"; }
3+
>Obj : Symbol(Obj, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 0, 0))
4+
>key : Symbol(Obj.key, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 0, 15))
5+
6+
async function fn1(): Promise<Obj> {
7+
>fn1 : Symbol(fn1, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 0, 31))
8+
>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, --, --), Decl(lib.es2018.promise.d.ts, --, --))
9+
>Obj : Symbol(Obj, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 0, 0))
10+
11+
const obj1: Obj = await { key: "value" };
12+
>obj1 : Symbol(obj1, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 3, 9))
13+
>Obj : Symbol(Obj, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 0, 0))
14+
>key : Symbol(key, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 3, 29))
15+
16+
const obj2: Obj = await new Promise(resolve => resolve({ key: "value" }));
17+
>obj2 : Symbol(obj2, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 4, 9))
18+
>Obj : Symbol(Obj, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 0, 0))
19+
>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, --, --), Decl(lib.es2018.promise.d.ts, --, --))
20+
>resolve : Symbol(resolve, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 4, 40))
21+
>resolve : Symbol(resolve, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 4, 40))
22+
>key : Symbol(key, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 4, 60))
23+
24+
return await { key: "value" };
25+
>key : Symbol(key, Decl(contextuallyTypeAsyncFunctionAwaitOperand.ts, 5, 18))
26+
}
27+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionAwaitOperand.ts ===
2+
interface Obj { key: "value"; }
3+
>key : "value"
4+
5+
async function fn1(): Promise<Obj> {
6+
>fn1 : () => Promise<Obj>
7+
8+
const obj1: Obj = await { key: "value" };
9+
>obj1 : Obj
10+
>await { key: "value" } : { key: "value"; }
11+
>{ key: "value" } : { key: "value"; }
12+
>key : "value"
13+
>"value" : "value"
14+
15+
const obj2: Obj = await new Promise(resolve => resolve({ key: "value" }));
16+
>obj2 : Obj
17+
>await new Promise(resolve => resolve({ key: "value" })) : Obj
18+
>new Promise(resolve => resolve({ key: "value" })) : Promise<Obj>
19+
>Promise : PromiseConstructor
20+
>resolve => resolve({ key: "value" }) : (resolve: (value?: Obj | PromiseLike<Obj>) => void) => void
21+
>resolve : (value?: Obj | PromiseLike<Obj>) => void
22+
>resolve({ key: "value" }) : void
23+
>resolve : (value?: Obj | PromiseLike<Obj>) => void
24+
>{ key: "value" } : { key: "value"; }
25+
>key : "value"
26+
>"value" : "value"
27+
28+
return await { key: "value" };
29+
>await { key: "value" } : { key: "value"; }
30+
>{ key: "value" } : { key: "value"; }
31+
>key : "value"
32+
>"value" : "value"
33+
}
34+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
=== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnType.ts ===
2+
interface Obj { key: "value"; }
3+
>Obj : Symbol(Obj, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 0, 0))
4+
>key : Symbol(Obj.key, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 0, 15))
5+
6+
async function fn1(): Promise<Obj> {
7+
>fn1 : Symbol(fn1, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 0, 31))
8+
>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, --, --), Decl(lib.es2018.promise.d.ts, --, --))
9+
>Obj : Symbol(Obj, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 0, 0))
10+
11+
return { key: "value" };
12+
>key : Symbol(key, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 3, 12))
13+
}
14+
15+
async function fn2(): Promise<Obj> {
16+
>fn2 : Symbol(fn2, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 4, 1))
17+
>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, --, --), Decl(lib.es2018.promise.d.ts, --, --))
18+
>Obj : Symbol(Obj, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 0, 0))
19+
20+
return new Promise(resolve => {
21+
>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, --, --), Decl(lib.es2018.promise.d.ts, --, --))
22+
>resolve : Symbol(resolve, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 7, 23))
23+
24+
resolve({ key: "value" });
25+
>resolve : Symbol(resolve, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 7, 23))
26+
>key : Symbol(key, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 8, 17))
27+
28+
});
29+
}
30+
31+
async function fn3(): Promise<Obj> {
32+
>fn3 : Symbol(fn3, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 10, 1))
33+
>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, --, --), Decl(lib.es2018.promise.d.ts, --, --))
34+
>Obj : Symbol(Obj, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 0, 0))
35+
36+
return await { key: "value" };
37+
>key : Symbol(key, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 13, 18))
38+
}
39+
40+
async function fn4(): Promise<Obj> {
41+
>fn4 : Symbol(fn4, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 14, 1))
42+
>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, --, --), Decl(lib.es2018.promise.d.ts, --, --))
43+
>Obj : Symbol(Obj, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 0, 0))
44+
45+
return await new Promise(resolve => {
46+
>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, --, --), Decl(lib.es2018.promise.d.ts, --, --))
47+
>resolve : Symbol(resolve, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 17, 29))
48+
49+
resolve({ key: "value" });
50+
>resolve : Symbol(resolve, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 17, 29))
51+
>key : Symbol(key, Decl(contextuallyTypeAsyncFunctionReturnType.ts, 18, 17))
52+
53+
});
54+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
=== tests/cases/conformance/types/contextualTypes/asyncFunctions/contextuallyTypeAsyncFunctionReturnType.ts ===
2+
interface Obj { key: "value"; }
3+
>key : "value"
4+
5+
async function fn1(): Promise<Obj> {
6+
>fn1 : () => Promise<Obj>
7+
8+
return { key: "value" };
9+
>{ key: "value" } : { key: "value"; }
10+
>key : "value"
11+
>"value" : "value"
12+
}
13+
14+
async function fn2(): Promise<Obj> {
15+
>fn2 : () => Promise<Obj>
16+
17+
return new Promise(resolve => {
18+
>new Promise(resolve => { resolve({ key: "value" }); }) : Promise<Obj>
19+
>Promise : PromiseConstructor
20+
>resolve => { resolve({ key: "value" }); } : (resolve: (value?: Obj | PromiseLike<Obj>) => void) => void
21+
>resolve : (value?: Obj | PromiseLike<Obj>) => void
22+
23+
resolve({ key: "value" });
24+
>resolve({ key: "value" }) : void
25+
>resolve : (value?: Obj | PromiseLike<Obj>) => void
26+
>{ key: "value" } : { key: "value"; }
27+
>key : "value"
28+
>"value" : "value"
29+
30+
});
31+
}
32+
33+
async function fn3(): Promise<Obj> {
34+
>fn3 : () => Promise<Obj>
35+
36+
return await { key: "value" };
37+
>await { key: "value" } : { key: "value"; }
38+
>{ key: "value" } : { key: "value"; }
39+
>key : "value"
40+
>"value" : "value"
41+
}
42+
43+
async function fn4(): Promise<Obj> {
44+
>fn4 : () => Promise<Obj>
45+
46+
return await new Promise(resolve => {
47+
>await new Promise(resolve => { resolve({ key: "value" }); }) : Obj
48+
>new Promise(resolve => { resolve({ key: "value" }); }) : Promise<Obj>
49+
>Promise : PromiseConstructor
50+
>resolve => { resolve({ key: "value" }); } : (resolve: (value?: Obj | PromiseLike<Obj>) => void) => void
51+
>resolve : (value?: Obj | PromiseLike<Obj>) => void
52+
53+
resolve({ key: "value" });
54+
>resolve({ key: "value" }) : void
55+
>resolve : (value?: Obj | PromiseLike<Obj>) => void
56+
>{ key: "value" } : { key: "value"; }
57+
>key : "value"
58+
>"value" : "value"
59+
60+
});
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @target: esnext
2+
// @noImplicitAny: true
3+
// @noEmit: true
4+
5+
interface Obj { key: "value"; }
6+
7+
async function fn1(): Promise<Obj> {
8+
const obj1: Obj = await { key: "value" };
9+
const obj2: Obj = await new Promise(resolve => resolve({ key: "value" }));
10+
return await { key: "value" };
11+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// @target: esnext
2+
// @noImplicitAny: true
3+
// @noEmit: true
4+
5+
interface Obj { key: "value"; }
6+
7+
async function fn1(): Promise<Obj> {
8+
return { key: "value" };
9+
}
10+
11+
async function fn2(): Promise<Obj> {
12+
return new Promise(resolve => {
13+
resolve({ key: "value" });
14+
});
15+
}
16+
17+
async function fn3(): Promise<Obj> {
18+
return await { key: "value" };
19+
}
20+
21+
async function fn4(): Promise<Obj> {
22+
return await new Promise(resolve => {
23+
resolve({ key: "value" });
24+
});
25+
}

0 commit comments

Comments
 (0)