Skip to content

Commit 309a1a3

Browse files
committed
Defer generic awaited type
1 parent 6769313 commit 309a1a3

23 files changed

+264
-228
lines changed

src/compiler/checker.ts

+18-90
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,7 @@ namespace ts {
820820
let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined;
821821
let deferredGlobalESSymbolType: ObjectType;
822822
let deferredGlobalTypedPropertyDescriptorType: GenericType;
823+
let deferredGlobalAwaitedSymbol: Symbol | undefined;
823824
let deferredGlobalPromiseType: GenericType;
824825
let deferredGlobalPromiseLikeType: GenericType;
825826
let deferredGlobalPromiseConstructorSymbol: Symbol | undefined;
@@ -874,7 +875,6 @@ namespace ts {
874875
const potentialThisCollisions: Node[] = [];
875876
const potentialNewTargetCollisions: Node[] = [];
876877
const potentialWeakMapCollisions: Node[] = [];
877-
const awaitedTypeStack: number[] = [];
878878

879879
const diagnostics = createDiagnosticCollection();
880880
const suggestionDiagnostics = createDiagnosticCollection();
@@ -11309,6 +11309,10 @@ namespace ts {
1130911309
return deferredGlobalESSymbolType || (deferredGlobalESSymbolType = getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
1131011310
}
1131111311

11312+
function getGlobalAwaitedSymbol(reportErrors: boolean) {
11313+
return deferredGlobalAwaitedSymbol || (deferredGlobalAwaitedSymbol = getGlobalTypeSymbol("Awaited" as __String, reportErrors));
11314+
}
11315+
1131211316
function getGlobalPromiseType(reportErrors: boolean) {
1131311317
return deferredGlobalPromiseType || (deferredGlobalPromiseType = getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
1131411318
}
@@ -26053,8 +26057,6 @@ namespace ts {
2605326057
// creates a `Promise<T>` type where `T` is the promisedType argument
2605426058
const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true);
2605526059
if (globalPromiseType !== emptyGenericType) {
26056-
// if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type
26057-
promisedType = getAwaitedType(promisedType) || unknownType;
2605826060
return createTypeReference(globalPromiseType, [promisedType]);
2605926061
}
2606026062

@@ -26065,8 +26067,6 @@ namespace ts {
2606526067
// creates a `PromiseLike<T>` type where `T` is the promisedType argument
2606626068
const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true);
2606726069
if (globalPromiseLikeType !== emptyGenericType) {
26068-
// if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type
26069-
promisedType = getAwaitedType(promisedType) || unknownType;
2607026070
return createTypeReference(globalPromiseLikeType, [promisedType]);
2607126071
}
2607226072

@@ -29528,98 +29528,26 @@ namespace ts {
2952829528
return typeAsAwaitable.awaitedTypeOfType = type;
2952929529
}
2953029530

29531-
if (type.flags & TypeFlags.Union) {
29532-
let types: Type[] | undefined;
29533-
for (const constituentType of (<UnionType>type).types) {
29534-
types = append<Type>(types, getAwaitedType(constituentType, errorNode, diagnosticMessage, arg0));
29535-
}
29536-
29537-
if (!types) {
29538-
return undefined;
29539-
}
29540-
29541-
return typeAsAwaitable.awaitedTypeOfType = getUnionType(types);
29531+
const symbol = getGlobalAwaitedSymbol(/*reportErrors*/ false);
29532+
if (!symbol) {
29533+
return typeAsAwaitable.awaitedTypeOfType = type;
2954229534
}
2954329535

29544-
const promisedType = getPromisedTypeOfPromise(type);
29545-
if (promisedType) {
29546-
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
29547-
// Verify that we don't have a bad actor in the form of a promise whose
29548-
// promised type is the same as the promise type, or a mutually recursive
29549-
// promise. If so, we return undefined as we cannot guess the shape. If this
29550-
// were the actual case in the JavaScript, this Promise would never resolve.
29551-
//
29552-
// An example of a bad actor with a singly-recursive promise type might
29553-
// be:
29554-
//
29555-
// interface BadPromise {
29556-
// then(
29557-
// onfulfilled: (value: BadPromise) => any,
29558-
// onrejected: (error: any) => any): BadPromise;
29559-
// }
29560-
// The above interface will pass the PromiseLike check, and return a
29561-
// promised type of `BadPromise`. Since this is a self reference, we
29562-
// don't want to keep recursing ad infinitum.
29563-
//
29564-
// An example of a bad actor in the form of a mutually-recursive
29565-
// promise type might be:
29566-
//
29567-
// interface BadPromiseA {
29568-
// then(
29569-
// onfulfilled: (value: BadPromiseB) => any,
29570-
// onrejected: (error: any) => any): BadPromiseB;
29571-
// }
29572-
//
29573-
// interface BadPromiseB {
29574-
// then(
29575-
// onfulfilled: (value: BadPromiseA) => any,
29576-
// onrejected: (error: any) => any): BadPromiseA;
29577-
// }
29578-
//
29579-
if (errorNode) {
29580-
error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method);
29581-
}
29582-
return undefined;
29583-
}
29584-
29585-
// Keep track of the type we're about to unwrap to avoid bad recursive promise types.
29586-
// See the comments above for more information.
29587-
awaitedTypeStack.push(type.id);
29588-
const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
29589-
awaitedTypeStack.pop();
29590-
29591-
if (!awaitedType) {
29592-
return undefined;
29593-
}
29536+
if (type.aliasSymbol === symbol) {
29537+
return typeAsAwaitable.awaitedTypeOfType = type;
29538+
}
2959429539

29595-
return typeAsAwaitable.awaitedTypeOfType = awaitedType;
29540+
const result = getTypeAliasInstantiation(symbol, [type]);
29541+
if (result !== unknownType || type === unknownType || getPromisedTypeOfPromise(type) === unknownType) {
29542+
return typeAsAwaitable.awaitedTypeOfType = result;
2959629543
}
2959729544

29598-
// The type was not a promise, so it could not be unwrapped any further.
29599-
// As long as the type does not have a callable "then" property, it is
29600-
// safe to return the type; otherwise, an error will be reported in
29601-
// the call to getNonThenableType and we will return undefined.
29602-
//
29603-
// An example of a non-promise "thenable" might be:
29604-
//
29605-
// await { then(): void {} }
29606-
//
29607-
// The "thenable" does not match the minimal definition for a promise. When
29608-
// a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise
29609-
// will never settle. We treat this as an error to help flag an early indicator
29610-
// of a runtime problem. If the user wants to return this value from an async
29611-
// function, they would need to wrap it in some other value. If they want it to
29612-
// be treated as a promise, they can cast to <any>.
29613-
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
29614-
if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) {
29615-
if (errorNode) {
29616-
if (!diagnosticMessage) return Debug.fail();
29617-
error(errorNode, diagnosticMessage, arg0);
29618-
}
29619-
return undefined;
29545+
if (errorNode) {
29546+
if (!diagnosticMessage) return Debug.fail();
29547+
error(errorNode, diagnosticMessage, arg0);
2962029548
}
2962129549

29622-
return typeAsAwaitable.awaitedTypeOfType = type;
29550+
return undefined;
2962329551
}
2962429552

2962529553
/**

src/harness/fourslashInterfaceImpl.ts

+1
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,7 @@ namespace FourSlashInterface {
957957
typeEntry("PropertyDecorator"),
958958
typeEntry("MethodDecorator"),
959959
typeEntry("ParameterDecorator"),
960+
typeEntry("Awaited"),
960961
typeEntry("PromiseConstructorLike"),
961962
interfaceEntry("PromiseLike"),
962963
interfaceEntry("Promise"),

src/lib/es5.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1378,6 +1378,11 @@ declare type PropertyDecorator = (target: Object, propertyKey: string | symbol)
13781378
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
13791379
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
13801380

1381+
// The undefined case is for strictNullChecks false, in which case
1382+
// undefined extends PromiseLike<infer U> is true, which would otherwise
1383+
// make Awaited<undefined> -> unknown.
1384+
type Awaited<T> = T extends undefined ? T : T extends PromiseLike<infer U> ? U : T extends { then: Function } ? unknown : T;
1385+
13811386
declare type PromiseConstructorLike = new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) => PromiseLike<T>;
13821387

13831388
interface PromiseLike<T> {

tests/baselines/reference/asyncArrowFunctionCapturesThis_es2017.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ class C {
66
>method : () => void
77

88
var fn = async () => await this;
9-
>fn : () => Promise<this>
10-
>async () => await this : () => Promise<this>
11-
>await this : this
9+
>fn : () => Promise<Awaited<this>>
10+
>async () => await this : () => Promise<Awaited<this>>
11+
>await this : Awaited<this>
1212
>this : this
1313
}
1414
}

tests/baselines/reference/asyncArrowFunctionCapturesThis_es5.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ class C {
66
>method : () => void
77

88
var fn = async () => await this;
9-
>fn : () => Promise<this>
10-
>async () => await this : () => Promise<this>
11-
>await this : this
9+
>fn : () => Promise<Awaited<this>>
10+
>async () => await this : () => Promise<Awaited<this>>
11+
>await this : Awaited<this>
1212
>this : this
1313
}
1414
}

tests/baselines/reference/asyncArrowFunctionCapturesThis_es6.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ class C {
66
>method : () => void
77

88
var fn = async () => await this;
9-
>fn : () => Promise<this>
10-
>async () => await this : () => Promise<this>
11-
>await this : this
9+
>fn : () => Promise<Awaited<this>>
10+
>async () => await this : () => Promise<Awaited<this>>
11+
>await this : Awaited<this>
1212
>this : this
1313
}
1414
}

tests/baselines/reference/asyncFunctionReturnType.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ async function fGenericIndexedTypeForExplicitPromiseOfAnyProp<TObj extends Obj>(
6363
return Promise.resolve<TObj["anyProp"]>(obj.anyProp);
6464
}
6565

66-
async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
66+
async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<Awaited<TObj[K]>> {
6767
return obj[key];
6868
}
6969

@@ -73,7 +73,8 @@ async function fGenericIndexedTypeForPromiseOfKProp<TObj extends Obj, K extends
7373

7474
async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
7575
return Promise.resolve<TObj[K]>(obj[key]);
76-
}
76+
}
77+
7778

7879
//// [asyncFunctionReturnType.js]
7980
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

tests/baselines/reference/asyncFunctionReturnType.symbols

+3-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ async function fGenericIndexedTypeForExplicitPromiseOfAnyProp<TObj extends Obj>(
221221
>anyProp : Symbol(Obj.anyProp, Decl(asyncFunctionReturnType.ts, 12, 23))
222222
}
223223

224-
async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
224+
async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<Awaited<TObj[K]>> {
225225
>fGenericIndexedTypeForKProp : Symbol(fGenericIndexedTypeForKProp, Decl(asyncFunctionReturnType.ts, 62, 1))
226226
>TObj : Symbol(TObj, Decl(asyncFunctionReturnType.ts, 64, 43))
227227
>Obj : Symbol(Obj, Decl(asyncFunctionReturnType.ts, 8, 1))
@@ -232,6 +232,7 @@ async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TOb
232232
>key : Symbol(key, Decl(asyncFunctionReturnType.ts, 64, 93))
233233
>K : Symbol(K, Decl(asyncFunctionReturnType.ts, 64, 60))
234234
>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, --, --))
235+
>Awaited : Symbol(Awaited, Decl(lib.es5.d.ts, --, --))
235236
>TObj : Symbol(TObj, Decl(asyncFunctionReturnType.ts, 64, 43))
236237
>K : Symbol(K, Decl(asyncFunctionReturnType.ts, 64, 60))
237238

@@ -285,3 +286,4 @@ async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K
285286
>obj : Symbol(obj, Decl(asyncFunctionReturnType.ts, 72, 100))
286287
>key : Symbol(key, Decl(asyncFunctionReturnType.ts, 72, 110))
287288
}
289+

tests/baselines/reference/asyncFunctionReturnType.types

+3-2
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ async function fGenericIndexedTypeForExplicitPromiseOfAnyProp<TObj extends Obj>(
180180
>anyProp : any
181181
}
182182

183-
async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
184-
>fGenericIndexedTypeForKProp : <TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K) => Promise<TObj[K]>
183+
async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<Awaited<TObj[K]>> {
184+
>fGenericIndexedTypeForKProp : <TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K) => Promise<Awaited<TObj[K]>>
185185
>obj : TObj
186186
>key : K
187187

@@ -220,3 +220,4 @@ async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K
220220
>obj : TObj
221221
>key : K
222222
}
223+

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)