Skip to content

Commit 90bd388

Browse files
committed
Defer generic awaited type
1 parent 485b07e commit 90bd388

9 files changed

+154
-99
lines changed

src/compiler/checker.ts

Lines changed: 24 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,6 @@ namespace ts {
853853
const flowNodeReachable: (boolean | undefined)[] = [];
854854
const potentialThisCollisions: Node[] = [];
855855
const potentialNewTargetCollisions: Node[] = [];
856-
const awaitedTypeStack: number[] = [];
857856

858857
const diagnostics = createDiagnosticCollection();
859858
const suggestionDiagnostics = createDiagnosticCollection();
@@ -29025,82 +29024,30 @@ namespace ts {
2902529024
return typeAsAwaitable.awaitedTypeOfType = getUnionType(types);
2902629025
}
2902729026

29028-
const promisedType = getPromisedTypeOfPromise(type);
29029-
if (promisedType) {
29030-
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
29031-
// Verify that we don't have a bad actor in the form of a promise whose
29032-
// promised type is the same as the promise type, or a mutually recursive
29033-
// promise. If so, we return undefined as we cannot guess the shape. If this
29034-
// were the actual case in the JavaScript, this Promise would never resolve.
29035-
//
29036-
// An example of a bad actor with a singly-recursive promise type might
29037-
// be:
29038-
//
29039-
// interface BadPromise {
29040-
// then(
29041-
// onfulfilled: (value: BadPromise) => any,
29042-
// onrejected: (error: any) => any): BadPromise;
29043-
// }
29044-
// The above interface will pass the PromiseLike check, and return a
29045-
// promised type of `BadPromise`. Since this is a self reference, we
29046-
// don't want to keep recursing ad infinitum.
29047-
//
29048-
// An example of a bad actor in the form of a mutually-recursive
29049-
// promise type might be:
29050-
//
29051-
// interface BadPromiseA {
29052-
// then(
29053-
// onfulfilled: (value: BadPromiseB) => any,
29054-
// onrejected: (error: any) => any): BadPromiseB;
29055-
// }
29056-
//
29057-
// interface BadPromiseB {
29058-
// then(
29059-
// onfulfilled: (value: BadPromiseA) => any,
29060-
// onrejected: (error: any) => any): BadPromiseA;
29061-
// }
29062-
//
29063-
if (errorNode) {
29064-
error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method);
29065-
}
29066-
return undefined;
29067-
}
29068-
29069-
// Keep track of the type we're about to unwrap to avoid bad recursive promise types.
29070-
// See the comments above for more information.
29071-
awaitedTypeStack.push(type.id);
29072-
const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
29073-
awaitedTypeStack.pop();
29074-
29075-
if (!awaitedType) {
29076-
return undefined;
29077-
}
29078-
29079-
return typeAsAwaitable.awaitedTypeOfType = awaitedType;
29080-
}
29081-
29082-
// The type was not a promise, so it could not be unwrapped any further.
29083-
// As long as the type does not have a callable "then" property, it is
29084-
// safe to return the type; otherwise, an error will be reported in
29085-
// the call to getNonThenableType and we will return undefined.
29086-
//
29087-
// An example of a non-promise "thenable" might be:
29088-
//
29089-
// await { then(): void {} }
29090-
//
29091-
// The "thenable" does not match the minimal definition for a promise. When
29092-
// a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise
29093-
// will never settle. We treat this as an error to help flag an early indicator
29094-
// of a runtime problem. If the user wants to return this value from an async
29095-
// function, they would need to wrap it in some other value. If they want it to
29096-
// be treated as a promise, they can cast to <any>.
29097-
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
29098-
if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) {
29099-
if (errorNode) {
29100-
if (!diagnosticMessage) return Debug.fail();
29101-
error(errorNode, diagnosticMessage, arg0);
29102-
}
29103-
return undefined;
29027+
const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ false);
29028+
if (globalPromiseLikeType !== emptyGenericType) {
29029+
const promisedType = createTypeParameter();
29030+
const result = getConditionalType({
29031+
node: undefined as unknown as ConditionalTypeNode,
29032+
checkType: type,
29033+
extendsType: undefinedType,
29034+
trueType: type,
29035+
falseType: getConditionalType({
29036+
node: undefined as unknown as ConditionalTypeNode,
29037+
checkType: type,
29038+
extendsType: createTypeReference(globalPromiseLikeType, [promisedType]),
29039+
trueType: promisedType,
29040+
falseType: type,
29041+
isDistributive: true,
29042+
inferTypeParameters: [promisedType],
29043+
outerTypeParameters: [type],
29044+
instantiations: createMap()
29045+
}, /*mapper*/ undefined),
29046+
isDistributive: true,
29047+
outerTypeParameters: [type],
29048+
instantiations: createMap()
29049+
}, /*mapper*/ undefined) as PromiseOrAwaitableType;
29050+
return typeAsAwaitable.awaitedTypeOfType = result.awaitedTypeOfType = result;
2910429051
}
2910529052

2910629053
return typeAsAwaitable.awaitedTypeOfType = type;

tests/baselines/reference/asyncFunctionDeclaration15_es5.errors.txt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1
88
Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
99
The types returned by 'then(...)' are incompatible between these types.
1010
Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.
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.
11+
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(10,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
1312

1413

15-
==== tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts (9 errors) ====
14+
==== tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts (8 errors) ====
1615
declare class Thenable { then(): void; }
1716
declare let a: any;
1817
declare let obj: { then: string; };
@@ -40,21 +39,19 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1
4039
!!! error TS1055: Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
4140
!!! error TS1055: The types returned by 'then(...)' are incompatible between these types.
4241
!!! error TS1055: Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.
42+
~~~~~~~~
43+
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
4344
async function fn7() { return; } // valid: Promise<void>
4445
async function fn8() { return 1; } // valid: Promise<number>
4546
async function fn9() { return null; } // valid: Promise<any>
4647
async function fn10() { return undefined; } // valid: Promise<any>
4748
async function fn11() { return a; } // valid: Promise<any>
4849
async function fn12() { return obj; } // valid: Promise<{ then: string; }>
4950
async function fn13() { return thenable; } // error
50-
~~~~
51-
!!! error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
5251
async function fn14() { await 1; } // valid: Promise<void>
5352
async function fn15() { await null; } // valid: Promise<void>
5453
async function fn16() { await undefined; } // valid: Promise<void>
5554
async function fn17() { await a; } // valid: Promise<void>
5655
async function fn18() { await obj; } // valid: Promise<void>
5756
async function fn19() { await thenable; } // error
58-
~~~~~~~~~~~~~~
59-
!!! error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.
6057

tests/baselines/reference/asyncFunctionDeclaration15_es5.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async function fn12() { return obj; } // valid: Promise<{ then: string; }>
5555
>obj : { then: string; }
5656

5757
async function fn13() { return thenable; } // error
58-
>fn13 : () => Promise<any>
58+
>fn13 : () => Promise<Thenable>
5959
>thenable : Thenable
6060

6161
async function fn14() { await 1; } // valid: Promise<void>
@@ -85,6 +85,6 @@ async function fn18() { await obj; } // valid: Promise<void>
8585

8686
async function fn19() { await thenable; } // error
8787
>fn19 : () => Promise<void>
88-
>await thenable : any
88+
>await thenable : Thenable
8989
>thenable : Thenable
9090

tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
55
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
66
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.
77
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.
8-
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.
9-
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.
8+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(10,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
109

1110

12-
==== tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts (9 errors) ====
11+
==== tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts (8 errors) ====
1312
declare class Thenable { then(): void; }
1413
declare let a: any;
1514
declare let obj: { then: string; };
@@ -34,21 +33,19 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
3433
async function fn6(): Thenable { } // error
3534
~~~~~~~~
3635
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
36+
~~~~~~~~
37+
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
3738
async function fn7() { return; } // valid: Promise<void>
3839
async function fn8() { return 1; } // valid: Promise<number>
3940
async function fn9() { return null; } // valid: Promise<any>
4041
async function fn10() { return undefined; } // valid: Promise<any>
4142
async function fn11() { return a; } // valid: Promise<any>
4243
async function fn12() { return obj; } // valid: Promise<{ then: string; }>
4344
async function fn13() { return thenable; } // error
44-
~~~~
45-
!!! error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
4645
async function fn14() { await 1; } // valid: Promise<void>
4746
async function fn15() { await null; } // valid: Promise<void>
4847
async function fn16() { await undefined; } // valid: Promise<void>
4948
async function fn17() { await a; } // valid: Promise<void>
5049
async function fn18() { await obj; } // valid: Promise<void>
5150
async function fn19() { await thenable; } // error
52-
~~~~~~~~~~~~~~
53-
!!! error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.
5451

tests/baselines/reference/asyncFunctionDeclaration15_es6.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async function fn12() { return obj; } // valid: Promise<{ then: string; }>
5555
>obj : { then: string; }
5656

5757
async function fn13() { return thenable; } // error
58-
>fn13 : () => Promise<any>
58+
>fn13 : () => Promise<Thenable>
5959
>thenable : Thenable
6060

6161
async function fn14() { await 1; } // valid: Promise<void>
@@ -85,6 +85,6 @@ async function fn18() { await obj; } // valid: Promise<void>
8585

8686
async function fn19() { await thenable; } // error
8787
>fn19 : () => Promise<void>
88-
>await thenable : any
88+
>await thenable : Thenable
8989
>thenable : Thenable
9090

tests/baselines/reference/compareTypeParameterConstrainedByLiteralToLiteral.errors.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ tests/cases/compiler/compareTypeParameterConstrainedByLiteralToLiteral.ts(5,5):
99
t === "x"; // Should be error
1010
~~~~~~~~~
1111
!!! error TS2367: This condition will always return 'false' since the types 'T' and '"x"' have no overlap.
12+
!!! related TS2773 tests/cases/compiler/compareTypeParameterConstrainedByLiteralToLiteral.ts:5:5: Did you forget to use 'await'?
1213
}
1314

0 commit comments

Comments
 (0)