Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defer generic awaited type #34883

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 26 additions & 79 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 @@ -4013,7 +4012,7 @@ namespace ts {
}
if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) {
if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) {
context.approximateLength += (symbolName(type.symbol).length + 6);
context.approximateLength += 6 + (type.symbol ? symbolName(type.symbol).length : 1);
return createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined));
}
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
Expand Down Expand Up @@ -4948,7 +4947,7 @@ namespace ts {
return cached;
}
}
let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true);
let result = type.symbol ? symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true) : createIdentifier("?");
if (!(result.kind & SyntaxKind.Identifier)) {
return createIdentifier("(Missing type parameter)");
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class C {
>method : () => void

var fn = async () => await this;
>fn : () => Promise<this>
>async () => await this : () => Promise<this>
>await this : this
>fn : () => Promise<this extends undefined ? this : this extends PromiseLike<infer ?> ? ? : this>
>async () => await this : () => Promise<this extends undefined ? this : this extends PromiseLike<infer ?> ? ? : this>
>await this : this extends undefined ? this : this extends PromiseLike<infer ?> ? ? : this
>this : this
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class C {
>method : () => void

var fn = async () => await this;
>fn : () => Promise<this>
>async () => await this : () => Promise<this>
>await this : this
>fn : () => Promise<this extends undefined ? this : this extends PromiseLike<infer ?> ? ? : this>
>async () => await this : () => Promise<this extends undefined ? this : this extends PromiseLike<infer ?> ? ? : this>
>await this : this extends undefined ? this : this extends PromiseLike<infer ?> ? ? : this
>this : this
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class C {
>method : () => void

var fn = async () => await this;
>fn : () => Promise<this>
>async () => await this : () => Promise<this>
>await this : this
>fn : () => Promise<this extends undefined ? this : this extends PromiseLike<infer ?> ? ? : this>
>async () => await this : () => Promise<this extends undefined ? this : this extends PromiseLike<infer ?> ? ? : this>
>await this : this extends undefined ? this : this extends PromiseLike<infer ?> ? ? : this
>this : this
}
}
Expand Down
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

17 changes: 16 additions & 1 deletion tests/baselines/reference/asyncFunctionReturnType.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,15 @@ async function fGenericIndexedTypeForPromiseOfKProp<TObj extends Obj, K extends

async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
return Promise.resolve<TObj[K]>(obj[key]);
}
}

// #27711

async function fGeneric<T>(x: T) {
return x;
}
const expected: Promise<string> = fGeneric(undefined as Promise<string>);


//// [asyncFunctionReturnType.js]
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
Expand Down Expand Up @@ -172,3 +180,10 @@ function fGenericIndexedTypeForExplicitPromiseOfKProp(obj, key) {
return Promise.resolve(obj[key]);
});
}
// #27711
function fGeneric(x) {
return __awaiter(this, void 0, void 0, function* () {
return x;
});
}
const expected = fGeneric(undefined);
19 changes: 19 additions & 0 deletions tests/baselines/reference/asyncFunctionReturnType.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,22 @@ async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K
>obj : Symbol(obj, Decl(asyncFunctionReturnType.ts, 72, 100))
>key : Symbol(key, Decl(asyncFunctionReturnType.ts, 72, 110))
}

// #27711

async function fGeneric<T>(x: T) {
>fGeneric : Symbol(fGeneric, Decl(asyncFunctionReturnType.ts, 74, 1))
>T : Symbol(T, Decl(asyncFunctionReturnType.ts, 78, 24))
>x : Symbol(x, Decl(asyncFunctionReturnType.ts, 78, 27))
>T : Symbol(T, Decl(asyncFunctionReturnType.ts, 78, 24))

return x;
>x : Symbol(x, Decl(asyncFunctionReturnType.ts, 78, 27))
}
const expected: Promise<string> = fGeneric(undefined as Promise<string>);
>expected : Symbol(expected, Decl(asyncFunctionReturnType.ts, 81, 5))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>fGeneric : Symbol(fGeneric, Decl(asyncFunctionReturnType.ts, 74, 1))
>undefined : Symbol(undefined)
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))

17 changes: 17 additions & 0 deletions tests/baselines/reference/asyncFunctionReturnType.types
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,20 @@ async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K
>obj : TObj
>key : K
}

// #27711

async function fGeneric<T>(x: T) {
>fGeneric : <T>(x: T) => Promise<T extends undefined ? T : T extends PromiseLike<infer ?> ? ? : T>
>x : T

return x;
>x : T
}
const expected: Promise<string> = fGeneric(undefined as Promise<string>);
>expected : Promise<string>
>fGeneric(undefined as Promise<string>) : Promise<string>
>fGeneric : <T>(x: T) => Promise<T extends undefined ? T : T extends PromiseLike<infer ?> ? ? : T>
>undefined as Promise<string> : Promise<string>
>undefined : undefined

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