Skip to content

Commit 2ca1de5

Browse files
authored
Merge pull request #15241 from Microsoft/fix15205
Yield in async generator should implicitly unwrap operand
2 parents e36d8d5 + 64e2c29 commit 2ca1de5

20 files changed

+690
-201
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15832,7 +15832,7 @@ namespace ts {
1583215832
// Promise/A+ compatible implementation will always assimilate any foreign promise, so the
1583315833
// return type of the body should be unwrapped to its awaited type, which we will wrap in
1583415834
// the native Promise<T> type later in this function.
15835-
type = checkAwaitedType(type, /*errorNode*/ func);
15835+
type = checkAwaitedType(type, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
1583615836
}
1583715837
}
1583815838
else {
@@ -15906,6 +15906,11 @@ namespace ts {
1590615906
// A yield* expression effectively yields everything that its operand yields
1590715907
type = checkIteratedTypeOrElementType(type, yieldExpression.expression, /*allowStringInput*/ false, (functionFlags & FunctionFlags.Async) !== 0);
1590815908
}
15909+
if (functionFlags & FunctionFlags.Async) {
15910+
type = checkAwaitedType(type, expr, yieldExpression.asteriskToken
15911+
? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member
15912+
: Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
15913+
}
1590915914
if (!contains(aggregatedTypes, type)) {
1591015915
aggregatedTypes.push(type);
1591115916
}
@@ -15955,7 +15960,7 @@ namespace ts {
1595515960
// Promise/A+ compatible implementation will always assimilate any foreign promise, so the
1595615961
// return type of the body should be unwrapped to its awaited type, which should be wrapped in
1595715962
// the native Promise<T> type by the caller.
15958-
type = checkAwaitedType(type, func);
15963+
type = checkAwaitedType(type, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
1595915964
}
1596015965
if (type.flags & TypeFlags.Never) {
1596115966
hasReturnOfTypeNever = true;
@@ -16132,7 +16137,7 @@ namespace ts {
1613216137
const exprType = checkExpression(<Expression>node.body);
1613316138
if (returnOrPromisedType) {
1613416139
if ((functionFlags & FunctionFlags.AsyncOrAsyncGenerator) === FunctionFlags.Async) { // Async function
16135-
const awaitedType = checkAwaitedType(exprType, node.body);
16140+
const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
1613616141
checkTypeAssignableTo(awaitedType, returnOrPromisedType, node.body);
1613716142
}
1613816143
else { // Normal function
@@ -16248,7 +16253,7 @@ namespace ts {
1624816253
}
1624916254

1625016255
const operandType = checkExpression(node.expression);
16251-
return checkAwaitedType(operandType, node);
16256+
return checkAwaitedType(operandType, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
1625216257
}
1625316258

1625416259
function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type {
@@ -16895,10 +16900,22 @@ namespace ts {
1689516900
if (func.type) {
1689616901
const signatureElementType = getIteratedTypeOfGenerator(getTypeFromTypeNode(func.type), (functionFlags & FunctionFlags.Async) !== 0) || anyType;
1689716902
if (nodeIsYieldStar) {
16898-
checkTypeAssignableTo(expressionElementType, signatureElementType, node.expression, /*headMessage*/ undefined);
16903+
checkTypeAssignableTo(
16904+
functionFlags & FunctionFlags.Async
16905+
? getAwaitedType(expressionElementType, node.expression, Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)
16906+
: expressionElementType,
16907+
signatureElementType,
16908+
node.expression,
16909+
/*headMessage*/ undefined);
1689916910
}
1690016911
else {
16901-
checkTypeAssignableTo(expressionType, signatureElementType, node.expression, /*headMessage*/ undefined);
16912+
checkTypeAssignableTo(
16913+
functionFlags & FunctionFlags.Async
16914+
? getAwaitedType(expressionType, node.expression, Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)
16915+
: expressionType,
16916+
signatureElementType,
16917+
node.expression,
16918+
/*headMessage*/ undefined);
1690216919
}
1690316920
}
1690416921
}
@@ -18233,9 +18250,9 @@ namespace ts {
1823318250
}
1823418251
}
1823518252

18236-
function getAwaitedTypeOfPromise(type: Type, errorNode?: Node): Type | undefined {
18253+
function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage): Type | undefined {
1823718254
const promisedType = getPromisedTypeOfPromise(type, errorNode);
18238-
return promisedType && getAwaitedType(promisedType, errorNode);
18255+
return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage);
1823918256
}
1824018257

1824118258
/**
@@ -18303,11 +18320,11 @@ namespace ts {
1830318320
* Promise-like type; otherwise, it is the type of the expression. This is used to reflect
1830418321
* The runtime behavior of the `await` keyword.
1830518322
*/
18306-
function checkAwaitedType(type: Type, errorNode: Node): Type {
18307-
return getAwaitedType(type, errorNode) || unknownType;
18323+
function checkAwaitedType(type: Type, errorNode: Node, diagnosticMessage: DiagnosticMessage): Type {
18324+
return getAwaitedType(type, errorNode, diagnosticMessage) || unknownType;
1830818325
}
1830918326

18310-
function getAwaitedType(type: Type, errorNode?: Node): Type | undefined {
18327+
function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage): Type | undefined {
1831118328
const typeAsAwaitable = <PromiseOrAwaitableType>type;
1831218329
if (typeAsAwaitable.awaitedTypeOfType) {
1831318330
return typeAsAwaitable.awaitedTypeOfType;
@@ -18320,7 +18337,7 @@ namespace ts {
1832018337
if (type.flags & TypeFlags.Union) {
1832118338
let types: Type[];
1832218339
for (const constituentType of (<UnionType>type).types) {
18323-
types = append(types, getAwaitedType(constituentType, errorNode));
18340+
types = append(types, getAwaitedType(constituentType, errorNode, diagnosticMessage));
1832418341
}
1832518342

1832618343
if (!types) {
@@ -18374,7 +18391,7 @@ namespace ts {
1837418391
// Keep track of the type we're about to unwrap to avoid bad recursive promise types.
1837518392
// See the comments above for more information.
1837618393
awaitedTypeStack.push(type.id);
18377-
const awaitedType = getAwaitedType(promisedType, errorNode);
18394+
const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage);
1837818395
awaitedTypeStack.pop();
1837918396

1838018397
if (!awaitedType) {
@@ -18402,7 +18419,8 @@ namespace ts {
1840218419
const thenFunction = getTypeOfPropertyOfType(type, "then");
1840318420
if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) {
1840418421
if (errorNode) {
18405-
error(errorNode, Diagnostics.Type_used_as_operand_to_await_or_the_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
18422+
Debug.assert(!!diagnosticMessage);
18423+
error(errorNode, diagnosticMessage);
1840618424
}
1840718425
return undefined;
1840818426
}
@@ -18513,7 +18531,7 @@ namespace ts {
1851318531
}
1851418532

1851518533
// Get and return the awaited type of the return type.
18516-
return checkAwaitedType(returnType, node);
18534+
return checkAwaitedType(returnType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
1851718535
}
1851818536

1851918537
/** Check a decorator */
@@ -19794,7 +19812,7 @@ namespace ts {
1979419812

1979519813
// For an async iterator, we must get the awaited type of the return type.
1979619814
if (isAsyncIterator) {
19797-
nextResult = getAwaitedTypeOfPromise(nextResult, errorNode);
19815+
nextResult = getAwaitedTypeOfPromise(nextResult, errorNode, Diagnostics.The_type_returned_by_the_next_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property);
1979819816
if (isTypeAny(nextResult)) {
1979919817
return undefined;
1980019818
}
@@ -19885,7 +19903,7 @@ namespace ts {
1988519903
else if (func.type || isGetAccessorWithAnnotatedSetAccessor(func)) {
1988619904
if (functionFlags & FunctionFlags.Async) { // Async function
1988719905
const promisedType = getPromisedTypeOfPromise(returnType);
19888-
const awaitedType = checkAwaitedType(exprType, node);
19906+
const awaitedType = checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
1988919907
if (promisedType) {
1989019908
// If the function has a return type, but promisedType is
1989119909
// undefined, an error will be reported in checkAsyncFunctionReturnType

src/compiler/diagnosticMessages.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@
175175
"category": "Error",
176176
"code": 1057
177177
},
178-
"Type used as operand to 'await' or the return type of an async function must either be a valid promise or must not contain a callable 'then' member.": {
178+
"The return type of an async function must either be a valid promise or must not contain a callable 'then' member.": {
179179
"category": "Error",
180180
"code": 1058
181181
},
@@ -867,6 +867,18 @@
867867
"category": "Error",
868868
"code": 1319
869869
},
870+
"Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.": {
871+
"category": "Error",
872+
"code": 1320
873+
},
874+
"Type of 'yield' operand in an async generator must either be a valid promise or must not contain a callable 'then' member.": {
875+
"category": "Error",
876+
"code": 1321
877+
},
878+
"Type of iterated elements of a 'yield*' operand must either be a valid promise or must not contain a callable 'then' member.": {
879+
"category": "Error",
880+
"code": 1322
881+
},
870882
"Duplicate identifier '{0}'.": {
871883
"category": "Error",
872884
"code": 2300

src/compiler/transformers/esnext.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,8 @@ namespace ts {
891891
function verb(n) { return function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]), next(); }); }; }
892892
function next() { if (!c && q.length) resume((c = q.shift())[0], c[1]); }
893893
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(c[3], e); } }
894-
function step(r) { r.done ? settle(c[2], r) : r.value[0] === "yield" ? settle(c[2], { value: r.value[1], done: false }) : Promise.resolve(r.value[1]).then(r.value[0] === "delegate" ? delegate : fulfill, reject); }
894+
function step(r) { r.done ? settle(c[2], r) : Promise.resolve(r.value[1]).then(r.value[0] === "yield" ? _yield : r.value[0] === "delegate" ? delegate : fulfill, reject); }
895+
function _yield(value) { settle(c[2], { value: value, done: false }); }
895896
function delegate(r) { step(r.done ? r : { value: ["yield", r.value], done: false }); }
896897
function fulfill(value) { resume("next", value); }
897898
function reject(value) { resume("throw", value); }

tests/baselines/reference/asyncFunctionDeclaration15_es5.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1
88
Types of property 'then' are incompatible.
99
Type '() => void' is not assignable to type '<TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => PromiseLike<TResult1 | TResult2>'.
1010
Type 'void' is not assignable to type 'PromiseLike<any>'.
11-
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(17,16): error TS1058: Type used as operand to 'await' or the return type of an async function must either be a valid promise or must not contain a callable 'then' member.
12-
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(23,25): error TS1058: Type used as operand to 'await' or the return type of an async function must either be a valid promise or must not contain a callable 'then' member.
11+
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(17,16): error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
12+
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(23,25): error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.
1313

1414

1515
==== tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts (8 errors) ====
@@ -47,13 +47,13 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1
4747
async function fn12() { return obj; } // valid: Promise<{ then: string; }>
4848
async function fn13() { return thenable; } // error
4949
~~~~
50-
!!! error TS1058: Type used as operand to 'await' or the return type of an async function must either be a valid promise or must not contain a callable 'then' member.
50+
!!! error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
5151
async function fn14() { await 1; } // valid: Promise<void>
5252
async function fn15() { await null; } // valid: Promise<void>
5353
async function fn16() { await undefined; } // valid: Promise<void>
5454
async function fn17() { await a; } // valid: Promise<void>
5555
async function fn18() { await obj; } // valid: Promise<void>
5656
async function fn19() { await thenable; } // error
5757
~~~~~~~~~~~~~~
58-
!!! error TS1058: Type used as operand to 'await' or the return type of an async function must either be a valid promise or must not contain a callable 'then' member.
58+
!!! error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.
5959

tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
33
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS1064: The return type of an async function or method must be the global Promise<T> type.
44
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(9,23): error TS1064: The return type of an async function or method must be the global Promise<T> type.
55
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(10,23): error TS1064: The return type of an async function or method must be the global Promise<T> type.
6-
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(17,16): error TS1058: Type used as operand to 'await' or the return type of an async function must either be a valid promise or must not contain a callable 'then' member.
7-
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(23,25): error TS1058: Type used as operand to 'await' or the return type of an async function must either be a valid promise or must not contain a callable 'then' member.
6+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(17,16): error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
7+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(23,25): error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.
88

99

1010
==== tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts (7 errors) ====
@@ -36,13 +36,13 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
3636
async function fn12() { return obj; } // valid: Promise<{ then: string; }>
3737
async function fn13() { return thenable; } // error
3838
~~~~
39-
!!! error TS1058: Type used as operand to 'await' or the return type of an async function must either be a valid promise or must not contain a callable 'then' member.
39+
!!! error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
4040
async function fn14() { await 1; } // valid: Promise<void>
4141
async function fn15() { await null; } // valid: Promise<void>
4242
async function fn16() { await undefined; } // valid: Promise<void>
4343
async function fn17() { await a; } // valid: Promise<void>
4444
async function fn18() { await obj; } // valid: Promise<void>
4545
async function fn19() { await thenable; } // error
4646
~~~~~~~~~~~~~~
47-
!!! error TS1058: Type used as operand to 'await' or the return type of an async function must either be a valid promise or must not contain a callable 'then' member.
47+
!!! error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.
4848

0 commit comments

Comments
 (0)