Skip to content

Commit b601045

Browse files
committed
Defer generic awaited type
1 parent 3b919e2 commit b601045

13 files changed

+154
-155
lines changed

Diff for: src/compiler/checker.ts

+24-77
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,6 @@ namespace ts {
873873
const potentialThisCollisions: Node[] = [];
874874
const potentialNewTargetCollisions: Node[] = [];
875875
const potentialWeakMapCollisions: Node[] = [];
876-
const awaitedTypeStack: number[] = [];
877876

878877
const diagnostics = createDiagnosticCollection();
879878
const suggestionDiagnostics = createDiagnosticCollection();
@@ -29392,82 +29391,30 @@ namespace ts {
2939229391
return typeAsAwaitable.awaitedTypeOfType = getUnionType(types);
2939329392
}
2939429393

29395-
const promisedType = getPromisedTypeOfPromise(type);
29396-
if (promisedType) {
29397-
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
29398-
// Verify that we don't have a bad actor in the form of a promise whose
29399-
// promised type is the same as the promise type, or a mutually recursive
29400-
// promise. If so, we return undefined as we cannot guess the shape. If this
29401-
// were the actual case in the JavaScript, this Promise would never resolve.
29402-
//
29403-
// An example of a bad actor with a singly-recursive promise type might
29404-
// be:
29405-
//
29406-
// interface BadPromise {
29407-
// then(
29408-
// onfulfilled: (value: BadPromise) => any,
29409-
// onrejected: (error: any) => any): BadPromise;
29410-
// }
29411-
// The above interface will pass the PromiseLike check, and return a
29412-
// promised type of `BadPromise`. Since this is a self reference, we
29413-
// don't want to keep recursing ad infinitum.
29414-
//
29415-
// An example of a bad actor in the form of a mutually-recursive
29416-
// promise type might be:
29417-
//
29418-
// interface BadPromiseA {
29419-
// then(
29420-
// onfulfilled: (value: BadPromiseB) => any,
29421-
// onrejected: (error: any) => any): BadPromiseB;
29422-
// }
29423-
//
29424-
// interface BadPromiseB {
29425-
// then(
29426-
// onfulfilled: (value: BadPromiseA) => any,
29427-
// onrejected: (error: any) => any): BadPromiseA;
29428-
// }
29429-
//
29430-
if (errorNode) {
29431-
error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method);
29432-
}
29433-
return undefined;
29434-
}
29435-
29436-
// Keep track of the type we're about to unwrap to avoid bad recursive promise types.
29437-
// See the comments above for more information.
29438-
awaitedTypeStack.push(type.id);
29439-
const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
29440-
awaitedTypeStack.pop();
29441-
29442-
if (!awaitedType) {
29443-
return undefined;
29444-
}
29445-
29446-
return typeAsAwaitable.awaitedTypeOfType = awaitedType;
29447-
}
29448-
29449-
// The type was not a promise, so it could not be unwrapped any further.
29450-
// As long as the type does not have a callable "then" property, it is
29451-
// safe to return the type; otherwise, an error will be reported in
29452-
// the call to getNonThenableType and we will return undefined.
29453-
//
29454-
// An example of a non-promise "thenable" might be:
29455-
//
29456-
// await { then(): void {} }
29457-
//
29458-
// The "thenable" does not match the minimal definition for a promise. When
29459-
// a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise
29460-
// will never settle. We treat this as an error to help flag an early indicator
29461-
// of a runtime problem. If the user wants to return this value from an async
29462-
// function, they would need to wrap it in some other value. If they want it to
29463-
// be treated as a promise, they can cast to <any>.
29464-
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
29465-
if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) {
29466-
if (errorNode) {
29467-
if (!diagnosticMessage) return Debug.fail();
29468-
error(errorNode, diagnosticMessage, arg0);
29469-
}
29470-
return undefined;
29394+
const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ false);
29395+
if (globalPromiseLikeType !== emptyGenericType) {
29396+
const promisedType = createTypeParameter();
29397+
const result = getConditionalType({
29398+
node: undefined as unknown as ConditionalTypeNode,
29399+
checkType: type,
29400+
extendsType: undefinedType,
29401+
trueType: type,
29402+
falseType: getConditionalType({
29403+
node: undefined as unknown as ConditionalTypeNode,
29404+
checkType: type,
29405+
extendsType: createTypeReference(globalPromiseLikeType, [promisedType]),
29406+
trueType: promisedType,
29407+
falseType: type,
29408+
isDistributive: true,
29409+
inferTypeParameters: [promisedType],
29410+
outerTypeParameters: [type],
29411+
instantiations: createMap()
29412+
}, /*mapper*/ undefined),
29413+
isDistributive: true,
29414+
outerTypeParameters: [type],
29415+
instantiations: createMap()
29416+
}, /*mapper*/ undefined) as PromiseOrAwaitableType;
29417+
return typeAsAwaitable.awaitedTypeOfType = result.awaitedTypeOfType = result;
2947129418
}
2947229419

2947329420
return typeAsAwaitable.awaitedTypeOfType = type;

Diff for: tests/baselines/reference/asyncArrowFunctionCapturesThis_es2017.types

-15
This file was deleted.

Diff for: tests/baselines/reference/asyncArrowFunctionCapturesThis_es5.types

-15
This file was deleted.

Diff for: tests/baselines/reference/asyncArrowFunctionCapturesThis_es6.types

-15
This file was deleted.

Diff for: tests/baselines/reference/asyncFunctionDeclaration15_es5.errors.txt

+4-7
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

Diff for: tests/baselines/reference/asyncFunctionDeclaration15_es5.types

+2-2
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

Diff for: tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt

+4-7
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

Diff for: tests/baselines/reference/asyncFunctionDeclaration15_es6.types

+2-2
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

Diff for: tests/baselines/reference/compareTypeParameterConstrainedByLiteralToLiteral.errors.txt

+1
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)