Skip to content

Commit

Permalink
Defer generic awaited type
Browse files Browse the repository at this point in the history
  • Loading branch information
jablko committed Jan 25, 2020
1 parent 3b919e2 commit 631ae9d
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 155 deletions.
101 changes: 24 additions & 77 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,6 @@ namespace ts {
const potentialThisCollisions: Node[] = [];
const potentialNewTargetCollisions: Node[] = [];
const potentialWeakMapCollisions: Node[] = [];
const awaitedTypeStack: number[] = [];

const diagnostics = createDiagnosticCollection();
const suggestionDiagnostics = createDiagnosticCollection();
Expand Down Expand Up @@ -29392,82 +29391,30 @@ namespace ts {
return typeAsAwaitable.awaitedTypeOfType = getUnionType(types);
}

const promisedType = getPromisedTypeOfPromise(type);
if (promisedType) {
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
// Verify that we don't have a bad actor in the form of a promise whose
// promised type is the same as the promise type, or a mutually recursive
// promise. If so, we return undefined as we cannot guess the shape. If this
// were the actual case in the JavaScript, this Promise would never resolve.
//
// An example of a bad actor with a singly-recursive promise type might
// be:
//
// interface BadPromise {
// then(
// onfulfilled: (value: BadPromise) => any,
// onrejected: (error: any) => any): BadPromise;
// }
// The above interface will pass the PromiseLike check, and return a
// promised type of `BadPromise`. Since this is a self reference, we
// don't want to keep recursing ad infinitum.
//
// An example of a bad actor in the form of a mutually-recursive
// promise type might be:
//
// interface BadPromiseA {
// then(
// onfulfilled: (value: BadPromiseB) => any,
// onrejected: (error: any) => any): BadPromiseB;
// }
//
// interface BadPromiseB {
// then(
// onfulfilled: (value: BadPromiseA) => any,
// onrejected: (error: any) => any): BadPromiseA;
// }
//
if (errorNode) {
error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method);
}
return undefined;
}

// Keep track of the type we're about to unwrap to avoid bad recursive promise types.
// See the comments above for more information.
awaitedTypeStack.push(type.id);
const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
awaitedTypeStack.pop();

if (!awaitedType) {
return undefined;
}

return typeAsAwaitable.awaitedTypeOfType = awaitedType;
}

// The type was not a promise, so it could not be unwrapped any further.
// As long as the type does not have a callable "then" property, it is
// safe to return the type; otherwise, an error will be reported in
// the call to getNonThenableType and we will return undefined.
//
// An example of a non-promise "thenable" might be:
//
// await { then(): void {} }
//
// The "thenable" does not match the minimal definition for a promise. When
// a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise
// will never settle. We treat this as an error to help flag an early indicator
// of a runtime problem. If the user wants to return this value from an async
// function, they would need to wrap it in some other value. If they want it to
// be treated as a promise, they can cast to <any>.
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) {
if (errorNode) {
if (!diagnosticMessage) return Debug.fail();
error(errorNode, diagnosticMessage, arg0);
}
return undefined;
const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ false);
if (globalPromiseLikeType !== emptyGenericType) {
const promisedType = createTypeParameter();
const result = getConditionalType({
node: undefined as unknown as ConditionalTypeNode,
checkType: type,
extendsType: undefinedType,
trueType: type,
falseType: getConditionalType({
node: undefined as unknown as ConditionalTypeNode,
checkType: type,
extendsType: createTypeReference(globalPromiseLikeType, [promisedType]),
trueType: promisedType,
falseType: type,
isDistributive: true,
inferTypeParameters: [promisedType],
outerTypeParameters: [type],
instantiations: createMap()
}, /*mapper*/ undefined),
isDistributive: true,
outerTypeParameters: [type],
instantiations: createMap()
}, /*mapper*/ undefined) as PromiseOrAwaitableType;
return typeAsAwaitable.awaitedTypeOfType = result.awaitedTypeOfType = result;
}

return typeAsAwaitable.awaitedTypeOfType = type;
Expand Down

This file was deleted.

15 changes: 0 additions & 15 deletions tests/baselines/reference/asyncArrowFunctionCapturesThis_es5.types

This file was deleted.

15 changes: 0 additions & 15 deletions tests/baselines/reference/asyncArrowFunctionCapturesThis_es6.types

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1
Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
The types returned by 'then(...)' are incompatible between these types.
Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.
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.
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.
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.


==== tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts (9 errors) ====
==== tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts (8 errors) ====
declare class Thenable { then(): void; }
declare let a: any;
declare let obj: { then: string; };
Expand Down Expand Up @@ -40,21 +39,19 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1
!!! error TS1055: Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
!!! error TS1055: The types returned by 'then(...)' are incompatible between these types.
!!! error TS1055: Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.
~~~~~~~~
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
async function fn7() { return; } // valid: Promise<void>
async function fn8() { return 1; } // valid: Promise<number>
async function fn9() { return null; } // valid: Promise<any>
async function fn10() { return undefined; } // valid: Promise<any>
async function fn11() { return a; } // valid: Promise<any>
async function fn12() { return obj; } // valid: Promise<{ then: string; }>
async function fn13() { return thenable; } // error
~~~~
!!! error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
async function fn14() { await 1; } // valid: Promise<void>
async function fn15() { await null; } // valid: Promise<void>
async function fn16() { await undefined; } // valid: Promise<void>
async function fn17() { await a; } // valid: Promise<void>
async function fn18() { await obj; } // valid: Promise<void>
async function fn19() { await thenable; } // error
~~~~~~~~~~~~~~
!!! error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.

Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async function fn12() { return obj; } // valid: Promise<{ then: string; }>
>obj : { then: string; }

async function fn13() { return thenable; } // error
>fn13 : () => Promise<any>
>fn13 : () => Promise<Thenable>
>thenable : Thenable

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

async function fn19() { await thenable; } // error
>fn19 : () => Promise<void>
>await thenable : any
>await thenable : Thenable
>thenable : Thenable

Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
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.
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.
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.
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.
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.
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.


==== tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts (9 errors) ====
==== tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts (8 errors) ====
declare class Thenable { then(): void; }
declare let a: any;
declare let obj: { then: string; };
Expand All @@ -34,21 +33,19 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
async function fn6(): Thenable { } // error
~~~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
~~~~~~~~
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
async function fn7() { return; } // valid: Promise<void>
async function fn8() { return 1; } // valid: Promise<number>
async function fn9() { return null; } // valid: Promise<any>
async function fn10() { return undefined; } // valid: Promise<any>
async function fn11() { return a; } // valid: Promise<any>
async function fn12() { return obj; } // valid: Promise<{ then: string; }>
async function fn13() { return thenable; } // error
~~~~
!!! error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
async function fn14() { await 1; } // valid: Promise<void>
async function fn15() { await null; } // valid: Promise<void>
async function fn16() { await undefined; } // valid: Promise<void>
async function fn17() { await a; } // valid: Promise<void>
async function fn18() { await obj; } // valid: Promise<void>
async function fn19() { await thenable; } // error
~~~~~~~~~~~~~~
!!! error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.

Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async function fn12() { return obj; } // valid: Promise<{ then: string; }>
>obj : { then: string; }

async function fn13() { return thenable; } // error
>fn13 : () => Promise<any>
>fn13 : () => Promise<Thenable>
>thenable : Thenable

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

async function fn19() { await thenable; } // error
>fn19 : () => Promise<void>
>await thenable : any
>await thenable : Thenable
>thenable : Thenable

Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ tests/cases/compiler/compareTypeParameterConstrainedByLiteralToLiteral.ts(5,5):
t === "x"; // Should be error
~~~~~~~~~
!!! error TS2367: This condition will always return 'false' since the types 'T' and '"x"' have no overlap.
!!! related TS2773 tests/cases/compiler/compareTypeParameterConstrainedByLiteralToLiteral.ts:5:5: Did you forget to use 'await'?
}

Loading

0 comments on commit 631ae9d

Please sign in to comment.