Skip to content

Commit 75d19ba

Browse files
committed
Defer generic awaited type
1 parent 196c0aa commit 75d19ba

9 files changed

+154
-99
lines changed

src/compiler/checker.ts

+24-77
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();
@@ -28928,82 +28927,30 @@ namespace ts {
2892828927
return typeAsAwaitable.awaitedTypeOfType = getUnionType(types);
2892928928
}
2893028929

28931-
const promisedType = getPromisedTypeOfPromise(type);
28932-
if (promisedType) {
28933-
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
28934-
// Verify that we don't have a bad actor in the form of a promise whose
28935-
// promised type is the same as the promise type, or a mutually recursive
28936-
// promise. If so, we return undefined as we cannot guess the shape. If this
28937-
// were the actual case in the JavaScript, this Promise would never resolve.
28938-
//
28939-
// An example of a bad actor with a singly-recursive promise type might
28940-
// be:
28941-
//
28942-
// interface BadPromise {
28943-
// then(
28944-
// onfulfilled: (value: BadPromise) => any,
28945-
// onrejected: (error: any) => any): BadPromise;
28946-
// }
28947-
// The above interface will pass the PromiseLike check, and return a
28948-
// promised type of `BadPromise`. Since this is a self reference, we
28949-
// don't want to keep recursing ad infinitum.
28950-
//
28951-
// An example of a bad actor in the form of a mutually-recursive
28952-
// promise type might be:
28953-
//
28954-
// interface BadPromiseA {
28955-
// then(
28956-
// onfulfilled: (value: BadPromiseB) => any,
28957-
// onrejected: (error: any) => any): BadPromiseB;
28958-
// }
28959-
//
28960-
// interface BadPromiseB {
28961-
// then(
28962-
// onfulfilled: (value: BadPromiseA) => any,
28963-
// onrejected: (error: any) => any): BadPromiseA;
28964-
// }
28965-
//
28966-
if (errorNode) {
28967-
error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method);
28968-
}
28969-
return undefined;
28970-
}
28971-
28972-
// Keep track of the type we're about to unwrap to avoid bad recursive promise types.
28973-
// See the comments above for more information.
28974-
awaitedTypeStack.push(type.id);
28975-
const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
28976-
awaitedTypeStack.pop();
28977-
28978-
if (!awaitedType) {
28979-
return undefined;
28980-
}
28981-
28982-
return typeAsAwaitable.awaitedTypeOfType = awaitedType;
28983-
}
28984-
28985-
// The type was not a promise, so it could not be unwrapped any further.
28986-
// As long as the type does not have a callable "then" property, it is
28987-
// safe to return the type; otherwise, an error will be reported in
28988-
// the call to getNonThenableType and we will return undefined.
28989-
//
28990-
// An example of a non-promise "thenable" might be:
28991-
//
28992-
// await { then(): void {} }
28993-
//
28994-
// The "thenable" does not match the minimal definition for a promise. When
28995-
// a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise
28996-
// will never settle. We treat this as an error to help flag an early indicator
28997-
// of a runtime problem. If the user wants to return this value from an async
28998-
// function, they would need to wrap it in some other value. If they want it to
28999-
// be treated as a promise, they can cast to <any>.
29000-
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
29001-
if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) {
29002-
if (errorNode) {
29003-
if (!diagnosticMessage) return Debug.fail();
29004-
error(errorNode, diagnosticMessage, arg0);
29005-
}
29006-
return undefined;
28930+
const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ false);
28931+
if (globalPromiseLikeType !== emptyGenericType) {
28932+
const promisedType = createTypeParameter();
28933+
const result = getConditionalType({
28934+
node: undefined as unknown as ConditionalTypeNode,
28935+
checkType: type,
28936+
extendsType: undefinedType,
28937+
trueType: type,
28938+
falseType: getConditionalType({
28939+
node: undefined as unknown as ConditionalTypeNode,
28940+
checkType: type,
28941+
extendsType: createTypeReference(globalPromiseLikeType, [promisedType]),
28942+
trueType: promisedType,
28943+
falseType: type,
28944+
isDistributive: true,
28945+
inferTypeParameters: [promisedType],
28946+
outerTypeParameters: [type],
28947+
instantiations: createMap()
28948+
}, /*mapper*/ undefined),
28949+
isDistributive: true,
28950+
outerTypeParameters: [type],
28951+
instantiations: createMap()
28952+
}, /*mapper*/ undefined) as PromiseOrAwaitableType;
28953+
return typeAsAwaitable.awaitedTypeOfType = result.awaitedTypeOfType = result;
2900728954
}
2900828955

2900928956
return typeAsAwaitable.awaitedTypeOfType = type;

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

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

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

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

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)