Skip to content

Commit c6ff5f3

Browse files
authored
Fix for Awaited<T> inference (#49748)
1 parent ad4ded8 commit c6ff5f3

File tree

6 files changed

+145
-26
lines changed

6 files changed

+145
-26
lines changed

src/compiler/checker.ts

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36601,6 +36601,37 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
3660136601
type;
3660236602
}
3660336603

36604+
function isAwaitedTypeNeeded(type: Type) {
36605+
// If this is already an `Awaited<T>`, we shouldn't wrap it. This helps to avoid `Awaited<Awaited<T>>` in higher-order.
36606+
if (isTypeAny(type) || isAwaitedTypeInstantiation(type)) {
36607+
return false;
36608+
}
36609+
36610+
// We only need `Awaited<T>` if `T` contains possibly non-primitive types.
36611+
if (isGenericObjectType(type)) {
36612+
const baseConstraint = getBaseConstraintOfType(type);
36613+
// We only need `Awaited<T>` if `T` has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`,
36614+
// or is promise-like.
36615+
if (!baseConstraint || (baseConstraint.flags & TypeFlags.AnyOrUnknown) || isEmptyObjectType(baseConstraint) || isThenableType(baseConstraint)) {
36616+
return true;
36617+
}
36618+
}
36619+
36620+
return false;
36621+
}
36622+
36623+
function tryCreateAwaitedType(type: Type): Type | undefined {
36624+
// Nothing to do if `Awaited<T>` doesn't exist
36625+
const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true);
36626+
if (awaitedSymbol) {
36627+
// Unwrap unions that may contain `Awaited<T>`, otherwise its possible to manufacture an `Awaited<Awaited<T> | U>` where
36628+
// an `Awaited<T | U>` would suffice.
36629+
return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]);
36630+
}
36631+
36632+
return undefined;
36633+
}
36634+
3660436635
function createAwaitedTypeIfNeeded(type: Type): Type {
3660536636
// We wrap type `T` in `Awaited<T>` based on the following conditions:
3660636637
// - `T` is not already an `Awaited<U>`, and
@@ -36610,28 +36641,10 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
3661036641
// - The base constraint of `T` is `any`, `unknown`, `object`, or `{}`, or
3661136642
// - The base constraint of `T` is an object type with a callable `then` method.
3661236643

36613-
if (isTypeAny(type)) {
36614-
return type;
36615-
}
36616-
36617-
// If this is already an `Awaited<T>`, just return it. This helps to avoid `Awaited<Awaited<T>>` in higher-order.
36618-
if (isAwaitedTypeInstantiation(type)) {
36619-
return type;
36620-
}
36621-
36622-
// Only instantiate `Awaited<T>` if `T` contains possibly non-primitive types.
36623-
if (isGenericObjectType(type)) {
36624-
const baseConstraint = getBaseConstraintOfType(type);
36625-
// Only instantiate `Awaited<T>` if `T` has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`,
36626-
// or is promise-like.
36627-
if (!baseConstraint || (baseConstraint.flags & TypeFlags.AnyOrUnknown) || isEmptyObjectType(baseConstraint) || isThenableType(baseConstraint)) {
36628-
// Nothing to do if `Awaited<T>` doesn't exist
36629-
const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true);
36630-
if (awaitedSymbol) {
36631-
// Unwrap unions that may contain `Awaited<T>`, otherwise its possible to manufacture an `Awaited<Awaited<T> | U>` where
36632-
// an `Awaited<T | U>` would suffice.
36633-
return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]);
36634-
}
36644+
if (isAwaitedTypeNeeded(type)) {
36645+
const awaitedType = tryCreateAwaitedType(type);
36646+
if (awaitedType) {
36647+
return awaitedType;
3663536648
}
3663636649
}
3663736650

@@ -36693,6 +36706,11 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
3669336706
return typeAsAwaitable.awaitedTypeOfType = mapped;
3669436707
}
3669536708

36709+
// If `type` is generic and should be wrapped in `Awaited<T>`, return it.
36710+
if (isAwaitedTypeNeeded(type)) {
36711+
return typeAsAwaitable.awaitedTypeOfType = type;
36712+
}
36713+
3669636714
const thisTypeForErrorOut: { value: Type | undefined } = { value: undefined };
3669736715
const promisedType = getPromisedTypeOfPromise(type, /*errorNode*/ undefined, thisTypeForErrorOut);
3669836716
if (promisedType) {

tests/baselines/reference/awaitedType.errors.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,13 @@ tests/cases/compiler/awaitedType.ts(22,12): error TS2589: Type instantiation is
168168

169169
// helps with tests where '.types' just prints out the type alias name
170170
type _Expect<TActual extends TExpected, TExpected> = TActual;
171-
171+
172+
// https://github.com/microsoft/TypeScript/issues/48320
173+
async function f17<T extends (...args: any[]) => Promise<any>>(fn: T) {
174+
const ret: Awaited<ReturnType<T>> = await fn(1, 2, 3);
175+
return ret;
176+
}
177+
async function f17_usage() {
178+
const x = await f17(async () => 123 as const);
179+
return { x };
180+
}

tests/baselines/reference/awaitedType.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,16 @@ async function f16<T extends number & { then(): void }>(x: T) {
160160

161161
// helps with tests where '.types' just prints out the type alias name
162162
type _Expect<TActual extends TExpected, TExpected> = TActual;
163-
163+
164+
// https://github.com/microsoft/TypeScript/issues/48320
165+
async function f17<T extends (...args: any[]) => Promise<any>>(fn: T) {
166+
const ret: Awaited<ReturnType<T>> = await fn(1, 2, 3);
167+
return ret;
168+
}
169+
async function f17_usage() {
170+
const x = await f17(async () => 123 as const);
171+
return { x };
172+
}
164173

165174
//// [awaitedType.js]
166175
async function main() {
@@ -263,3 +272,12 @@ async function f16(x) {
263272
// y: T
264273
const y = await x;
265274
}
275+
// https://github.com/microsoft/TypeScript/issues/48320
276+
async function f17(fn) {
277+
const ret = await fn(1, 2, 3);
278+
return ret;
279+
}
280+
async function f17_usage() {
281+
const x = await f17(async () => 123);
282+
return { x };
283+
}

tests/baselines/reference/awaitedType.symbols

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,33 @@ type _Expect<TActual extends TExpected, TExpected> = TActual;
403403
>TExpected : Symbol(TExpected, Decl(awaitedType.ts, 160, 39))
404404
>TActual : Symbol(TActual, Decl(awaitedType.ts, 160, 13))
405405

406+
// https://github.com/microsoft/TypeScript/issues/48320
407+
async function f17<T extends (...args: any[]) => Promise<any>>(fn: T) {
408+
>f17 : Symbol(f17, Decl(awaitedType.ts, 160, 61))
409+
>T : Symbol(T, Decl(awaitedType.ts, 163, 19))
410+
>args : Symbol(args, Decl(awaitedType.ts, 163, 30))
411+
>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, --, --), Decl(lib.es2018.promise.d.ts, --, --))
412+
>fn : Symbol(fn, Decl(awaitedType.ts, 163, 63))
413+
>T : Symbol(T, Decl(awaitedType.ts, 163, 19))
414+
415+
const ret: Awaited<ReturnType<T>> = await fn(1, 2, 3);
416+
>ret : Symbol(ret, Decl(awaitedType.ts, 164, 9))
417+
>Awaited : Symbol(Awaited, Decl(lib.es5.d.ts, --, --))
418+
>ReturnType : Symbol(ReturnType, Decl(lib.es5.d.ts, --, --))
419+
>T : Symbol(T, Decl(awaitedType.ts, 163, 19))
420+
>fn : Symbol(fn, Decl(awaitedType.ts, 163, 63))
421+
422+
return ret;
423+
>ret : Symbol(ret, Decl(awaitedType.ts, 164, 9))
424+
}
425+
async function f17_usage() {
426+
>f17_usage : Symbol(f17_usage, Decl(awaitedType.ts, 166, 1))
427+
428+
const x = await f17(async () => 123 as const);
429+
>x : Symbol(x, Decl(awaitedType.ts, 168, 9))
430+
>f17 : Symbol(f17, Decl(awaitedType.ts, 160, 61))
431+
>const : Symbol(const)
432+
433+
return { x };
434+
>x : Symbol(x, Decl(awaitedType.ts, 169, 12))
435+
}

tests/baselines/reference/awaitedType.types

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ async function f11<T extends { then(onfulfilled: (value: unknown) => void): void
278278

279279
// y: Awaited<T>
280280
const y = await x;
281-
>y : unknown
282-
>await x : unknown
281+
>y : Awaited<T>
282+
>await x : Awaited<T>
283283
>x : T
284284
}
285285

@@ -358,3 +358,37 @@ async function f16<T extends number & { then(): void }>(x: T) {
358358
type _Expect<TActual extends TExpected, TExpected> = TActual;
359359
>_Expect : TActual
360360

361+
// https://github.com/microsoft/TypeScript/issues/48320
362+
async function f17<T extends (...args: any[]) => Promise<any>>(fn: T) {
363+
>f17 : <T extends (...args: any[]) => Promise<any>>(fn: T) => Promise<ReturnType<T>>
364+
>args : any[]
365+
>fn : T
366+
367+
const ret: Awaited<ReturnType<T>> = await fn(1, 2, 3);
368+
>ret : Awaited<ReturnType<T>>
369+
>await fn(1, 2, 3) : any
370+
>fn(1, 2, 3) : Promise<any>
371+
>fn : T
372+
>1 : 1
373+
>2 : 2
374+
>3 : 3
375+
376+
return ret;
377+
>ret : Awaited<ReturnType<T>>
378+
}
379+
async function f17_usage() {
380+
>f17_usage : () => Promise<{ x: 123; }>
381+
382+
const x = await f17(async () => 123 as const);
383+
>x : 123
384+
>await f17(async () => 123 as const) : 123
385+
>f17(async () => 123 as const) : Promise<Promise<123>>
386+
>f17 : <T extends (...args: any[]) => Promise<any>>(fn: T) => Promise<ReturnType<T>>
387+
>async () => 123 as const : () => Promise<123>
388+
>123 as const : 123
389+
>123 : 123
390+
391+
return { x };
392+
>{ x } : { x: 123; }
393+
>x : 123
394+
}

tests/cases/compiler/awaitedType.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,13 @@ async function f16<T extends number & { then(): void }>(x: T) {
162162

163163
// helps with tests where '.types' just prints out the type alias name
164164
type _Expect<TActual extends TExpected, TExpected> = TActual;
165+
166+
// https://github.com/microsoft/TypeScript/issues/48320
167+
async function f17<T extends (...args: any[]) => Promise<any>>(fn: T) {
168+
const ret: Awaited<ReturnType<T>> = await fn(1, 2, 3);
169+
return ret;
170+
}
171+
async function f17_usage() {
172+
const x = await f17(async () => 123 as const);
173+
return { x };
174+
}

0 commit comments

Comments
 (0)