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

Adds 'Awaited' type alias and updates to Promise.all/race/allSettled/any #45350

Merged
merged 4 commits into from
Sep 10, 2021
Merged
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
255 changes: 184 additions & 71 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/harness/fourslashInterfaceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,7 @@ namespace FourSlashInterface {
typeEntry("PromiseConstructorLike"),
interfaceEntry("PromiseLike"),
interfaceEntry("Promise"),
typeEntry("Awaited"),
interfaceEntry("ArrayLike"),
typeEntry("Partial"),
typeEntry("Required"),
Expand Down
12 changes: 2 additions & 10 deletions src/lib/es2015.iterable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,23 +203,15 @@ interface PromiseConstructor {
* @param values An iterable of Promises.
* @returns A new Promise.
*/
all<T>(values: Iterable<T | PromiseLike<T>>): Promise<T[]>;
all<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]>;

/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An iterable of Promises.
* @returns A new Promise.
*/
race<T>(values: Iterable<T>): Promise<T extends PromiseLike<infer U> ? U : T>;

/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An iterable of Promises.
* @returns A new Promise.
*/
race<T>(values: Iterable<T | PromiseLike<T>>): Promise<T>;
race<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>>;
}

interface String {
Expand Down
76 changes: 2 additions & 74 deletions src/lib/es2015.promise.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,79 +18,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7, T8>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): Promise<[T1, T2, T3, T4, T5, T6, T7]>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): Promise<[T1, T2, T3, T4, T5, T6]>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>]): Promise<[T1, T2, T3, T4, T5]>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>]): Promise<[T1, T2, T3, T4]>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;

/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T>(values: readonly (T | PromiseLike<T>)[]): Promise<T[]>;
all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;

// see: lib.es2015.iterable.d.ts
// all<T>(values: Iterable<T | PromiseLike<T>>): Promise<T[]>;
Expand All @@ -101,7 +29,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T>(values: readonly T[]): Promise<T extends PromiseLike<infer U> ? U : T>;
race<T extends readonly unknown[] | []>(values: T): Promise<Awaited<T[number]>>;

// see: lib.es2015.iterable.d.ts
// race<T>(values: Iterable<T>): Promise<T extends PromiseLike<infer U> ? U : T>;
Expand Down
5 changes: 2 additions & 3 deletions src/lib/es2020.promise.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
allSettled<T extends readonly unknown[] | readonly [unknown]>(values: T):
Promise<{ -readonly [P in keyof T]: PromiseSettledResult<T[P] extends PromiseLike<infer U> ? U : T[P]> }>;
allSettled<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>> }>;

/**
* Creates a Promise that is resolved with an array of results when all
* of the provided Promises resolve or reject.
* @param values An array of Promises.
* @returns A new Promise.
*/
allSettled<T>(values: Iterable<T>): Promise<PromiseSettledResult<T extends PromiseLike<infer U> ? U : T>[]>;
allSettled<T>(values: Iterable<T | PromiseLike<T>>): Promise<PromiseSettledResult<Awaited<T>>[]>;
}
9 changes: 8 additions & 1 deletion src/lib/es2021.promise.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,12 @@ interface PromiseConstructor {
* @param values An array or iterable of Promises.
* @returns A new Promise.
*/
any<T>(values: (T | PromiseLike<T>)[] | Iterable<T | PromiseLike<T>>): Promise<T>
any<T extends readonly unknown[] | []>(values: T): Promise<Awaited<T[number]>>;

/**
* The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError containing an array of rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm.
* @param values An array or iterable of Promises.
* @returns A new Promise.
*/
any<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>>
}
13 changes: 13 additions & 0 deletions src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,19 @@ interface Promise<T> {
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}

/**
* Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`.
*/
type Awaited<T> =
T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
T extends object ? // `await` only unwraps object types with a callable then. Non-object types are not unwrapped.
T extends { then(onfulfilled: infer F): any } ? // thenable, extracts the first argument to `then()`
F extends ((value: infer V) => any) ? // if the argument to `then` is callable, extracts the argument
Awaited<V> : // recursively unwrap the value
never : // the argument to `then` was not callable.
T : // argument was not an object
T; // non-thenable
Comment on lines +1453 to +1454
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that these comments are swapped.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently so. I will push up a small fix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by #45918


interface ArrayLike<T> {
readonly length: number;
readonly [n: number]: T;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction9_es20
!!! error TS1005: ',' expected.
~~~~~~~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:150:13: 'Promise' was also declared here.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:78:13: 'Promise' was also declared here.
~
!!! error TS1005: ',' expected.
~~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction9_es5.ts(
!!! error TS1005: ',' expected.
~~~~~~~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:150:13: 'Promise' was also declared here.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:78:13: 'Promise' was also declared here.
~
!!! error TS1005: ',' expected.
~~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tests/cases/conformance/async/es6/asyncArrowFunction/asyncArrowFunction9_es6.ts(
!!! error TS1005: ',' expected.
~~~~~~~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:150:13: 'Promise' was also declared here.
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:78:13: 'Promise' was also declared here.
~
!!! error TS1005: ',' expected.
~~
Expand Down
168 changes: 168 additions & 0 deletions tests/baselines/reference/awaitedType.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
tests/cases/compiler/awaitedType.ts(18,12): error TS2589: Type instantiation is excessively deep and possibly infinite.
tests/cases/compiler/awaitedType.ts(22,12): error TS2589: Type instantiation is excessively deep and possibly infinite.


==== tests/cases/compiler/awaitedType.ts (2 errors) ====
type T1 = Awaited<number>;
type T2 = Awaited<Promise<number>>;
type T3 = Awaited<number | Promise<number>>;
type T4 = Awaited<number | Promise<string>>;
type T5 = Awaited<{ then: number }>;
type T6 = Awaited<{ then(): void }>; // never (non-promise "thenable")
type T7 = Awaited<{ then(x: number): void }>; // never (non-promise "thenable")
type T8 = Awaited<{ then(x: () => void): void }>; // unknown
type T9 = Awaited<any>;
type T10 = Awaited<never>;
type T11 = Awaited<unknown>;
type T12 = Awaited<Promise<Promise<number>>>;
type T13 = _Expect<Awaited<Promise<Promise<number>> | string | null>, /*expected*/ string | number | null>; // otherwise just prints T13 in types tests, which isn't very helpful
type T14 = _Expect<Awaited<Promise<Promise<number>> | string | undefined>, /*expected*/ string | number | undefined>; // otherwise just prints T14 in types tests, which isn't very helpful
type T15 = _Expect<Awaited<Promise<Promise<number>> | string | null | undefined>, /*expected*/ string | number | null | undefined>; // otherwise just prints T15 in types tests, which isn't very helpful

interface BadPromise { then(cb: (value: BadPromise) => void): void; }
type T16 = Awaited<BadPromise>; // error
~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.

interface BadPromise1 { then(cb: (value: BadPromise2) => void): void; }
interface BadPromise2 { then(cb: (value: BadPromise1) => void): void; }
type T17 = Awaited<BadPromise1>; // error
~~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.

// https://github.com/microsoft/TypeScript/issues/33562
type MaybePromise<T> = T | Promise<T> | PromiseLike<T>
declare function MaybePromise<T>(value: T): MaybePromise<T>;

async function main() {
let aaa: number;
let bbb: string;
[
aaa,
bbb,
] = await Promise.all([
MaybePromise(1),
MaybePromise('2'),
MaybePromise(true),
])
}

// non-generic
async function f1(x: string) {
// y: string
const y = await x;
}

async function f2(x: unknown) {
// y: unknown
const y = await x;
}

async function f3(x: object) {
// y: object
const y = await x;
}

async function f4(x: Promise<string>) {
// y: string
const y = await x;
}

async function f5(x: Promise<unknown>) {
// y: unknown
const y = await x;
}

async function f6(x: Promise<object>) {
// y: object
const y = await x;
}

// generic

async function f7<T>(x: T) {
// NOTE: T does not belong solely to the domain of primitive types and either does
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
// or it has a non-primitive base constraint with a callable `then`.

// y: Awaited<T>
const y = await x;
}

async function f8<T extends any>(x: T) {
// NOTE: T does not belong solely to the domain of primitive types and either does
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
// or it has a non-primitive base constraint with a callable `then`.

// y: Awaited<T>
const y = await x;
}

async function f9<T extends unknown>(x: T) {
// NOTE: T does not belong solely to the domain of primitive types and either does
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
// or it has a non-primitive base constraint with a callable `then`.

// y: Awaited<T>
const y = await x;
}

async function f10<T extends {}>(x: T) {
// NOTE: T does not belong solely to the domain of primitive types and either does
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
// or it has a non-primitive base constraint with a callable `then`.

// y: Awaited<T>
const y = await x;
}

async function f11<T extends { then(onfulfilled: (value: unknown) => void): void }>(x: T) {
// NOTE: T does not belong solely to the domain of primitive types and either does
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
// or it has a non-primitive base constraint with a callable `then`.

// y: Awaited<T>
const y = await x;
}

async function f12<T extends string | object>(x: T) {
// NOTE: T does not belong solely to the domain of primitive types and either does
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
// or it has a non-primitive base constraint with a callable `then`.

// y: Awaited<T>
const y = await x;
}

async function f13<T extends string>(x: T) {
// NOTE: T belongs to the domain of primitive types

// y: T
const y = await x;
}

async function f14<T extends { x: number }>(x: T) {
// NOTE: T has a non-primitive base constraint without a callable `then`.

// y: T
const y = await x;
}

async function f15<T extends { then: number }>(x: T) {
// NOTE: T has a non-primitive base constraint without a callable `then`.

// y: T
const y = await x;
}

async function f16<T extends number & { then(): void }>(x: T) {
// NOTE: T belongs to the domain of primitive types (regardless of `then`)

// y: T
const y = await x;
}


// helps with tests where '.types' just prints out the type alias name
type _Expect<TActual extends TExpected, TExpected> = TActual;

Loading