Skip to content

Commit 09f78b1

Browse files
committed
Better typings for Promise.all(), etc. like #31117
1 parent b16e440 commit 09f78b1

File tree

92 files changed

+1203
-1306
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+1203
-1306
lines changed

Diff for: src/lib/es2015.iterable.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ interface PromiseConstructor {
203203
* @param values An array of Promises.
204204
* @returns A new Promise.
205205
*/
206-
all<TAll>(values: Iterable<TAll | PromiseLike<TAll>>): Promise<TAll[]>;
206+
all<TAll>(values: Iterable<TAll>): Promise<(TAll extends PromiseLike<infer UAll> ? UAll : TAll)[]>;
207207
}
208208

209209
declare namespace Reflect {

Diff for: src/lib/es2015.promise.d.ts

+6-84
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
2+
13
interface PromiseConstructor {
24
/**
35
* A reference to the prototype.
@@ -18,95 +20,15 @@ interface PromiseConstructor {
1820
* @param values An array of Promises.
1921
* @returns A new Promise.
2022
*/
21-
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]>;
22-
23-
/**
24-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
25-
* resolve, or rejected when any Promise is rejected.
26-
* @param values An array of Promises.
27-
* @returns A new Promise.
28-
*/
29-
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]>;
30-
31-
/**
32-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
33-
* resolve, or rejected when any Promise is rejected.
34-
* @param values An array of Promises.
35-
* @returns A new Promise.
36-
*/
37-
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]>;
38-
39-
/**
40-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
41-
* resolve, or rejected when any Promise is rejected.
42-
* @param values An array of Promises.
43-
* @returns A new Promise.
44-
*/
45-
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]>;
46-
47-
/**
48-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
49-
* resolve, or rejected when any Promise is rejected.
50-
* @param values An array of Promises.
51-
* @returns A new Promise.
52-
*/
53-
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]>;
54-
55-
/**
56-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
57-
* resolve, or rejected when any Promise is rejected.
58-
* @param values An array of Promises.
59-
* @returns A new Promise.
60-
*/
61-
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]>;
62-
63-
/**
64-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
65-
* resolve, or rejected when any Promise is rejected.
66-
* @param values An array of Promises.
67-
* @returns A new Promise.
68-
*/
69-
all<T1, T2, T3, T4>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>]): Promise<[T1, T2, T3, T4]>;
70-
71-
/**
72-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
73-
* resolve, or rejected when any Promise is rejected.
74-
* @param values An array of Promises.
75-
* @returns A new Promise.
76-
*/
77-
all<T1, T2, T3>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
78-
79-
/**
80-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
81-
* resolve, or rejected when any Promise is rejected.
82-
* @param values An array of Promises.
83-
* @returns A new Promise.
84-
*/
85-
all<T1, T2>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
86-
87-
/**
88-
* Creates a Promise that is resolved with an array of results when all of the provided Promises
89-
* resolve, or rejected when any Promise is rejected.
90-
* @param values An array of Promises.
91-
* @returns A new Promise.
92-
*/
93-
all<T>(values: readonly (T | PromiseLike<T>)[]): Promise<T[]>;
94-
95-
/**
96-
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
97-
* or rejected.
98-
* @param values An array of Promises.
99-
* @returns A new Promise.
100-
*/
101-
race<T>(values: readonly T[]): Promise<T extends PromiseLike<infer U> ? U : T>;
23+
all<T extends readonly any[]>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;
10224

10325
/**
10426
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
10527
* or rejected.
10628
* @param values An iterable of Promises.
10729
* @returns A new Promise.
10830
*/
109-
race<T>(values: Iterable<T>): Promise<T extends PromiseLike<infer U> ? U : T>;
31+
race<T>(values: Iterable<T>): Promise<Awaited<T>>;
11032

11133
/**
11234
* Creates a new rejected promise for the provided reason.
@@ -120,10 +42,10 @@ interface PromiseConstructor {
12042
* @param value A promise.
12143
* @returns A promise whose internal state matches the provided promise.
12244
*/
123-
resolve<T>(value: T | PromiseLike<T>): Promise<T>;
45+
resolve<T>(value: T): Promise<Awaited<T>>;
12446

12547
/**
126-
* Creates a new resolved promise .
48+
* Creates a new resolved promise.
12749
* @returns A resolved promise.
12850
*/
12951
resolve(): Promise<void>;

Diff for: src/lib/es2018.asyncgenerator.d.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> {
44
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
5-
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
6-
return(value: TReturn | PromiseLike<TReturn>): Promise<IteratorResult<T, TReturn>>;
7-
throw(e: any): Promise<IteratorResult<T, TReturn>>;
5+
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn extends PromiseLike<infer UReturn> ? UReturn : TReturn>>;
6+
return(value: TReturn): Promise<IteratorResult<T, TReturn extends PromiseLike<infer UReturn> ? UReturn : TReturn>>;
7+
throw(e: any): Promise<IteratorResult<T, TReturn extends PromiseLike<infer UReturn> ? UReturn : TReturn>>;
88
[Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>;
99
}
1010

Diff for: src/lib/es2018.asynciterable.d.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ interface SymbolConstructor {
1111

1212
interface AsyncIterator<T, TReturn = any, TNext = undefined> {
1313
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
14-
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
15-
return?(value?: TReturn | PromiseLike<TReturn>): Promise<IteratorResult<T, TReturn>>;
16-
throw?(e?: any): Promise<IteratorResult<T, TReturn>>;
14+
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn extends PromiseLike<infer UReturn> ? UReturn : TReturn>>;
15+
return?(value?: TReturn): Promise<IteratorResult<T, TReturn extends PromiseLike<infer UReturn> ? UReturn : TReturn>>;
16+
throw?(e?: any): Promise<IteratorResult<T, TReturn extends PromiseLike<infer UReturn> ? UReturn : TReturn>>;
1717
}
1818

1919
interface AsyncIterable<T> {
@@ -22,4 +22,4 @@ interface AsyncIterable<T> {
2222

2323
interface AsyncIterableIterator<T> extends AsyncIterator<T> {
2424
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
25-
}
25+
}

Diff for: src/lib/es5.d.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1387,7 +1387,7 @@ interface PromiseLike<T> {
13871387
* @param onrejected The callback to execute when the Promise is rejected.
13881388
* @returns A Promise for the completion of which ever callback is executed.
13891389
*/
1390-
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
1390+
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1) | null, onrejected?: ((reason: any) => TResult2) | null): PromiseLike<(TResult1 extends PromiseLike<infer UResult1> ? UResult1 : TResult1) | (TResult2 extends PromiseLike<infer UResult2> ? UResult2 : TResult2)>;
13911391
}
13921392

13931393
/**
@@ -1400,14 +1400,14 @@ interface Promise<T> {
14001400
* @param onrejected The callback to execute when the Promise is rejected.
14011401
* @returns A Promise for the completion of which ever callback is executed.
14021402
*/
1403-
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
1403+
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1) | null, onrejected?: ((reason: any) => TResult2) | null): Promise<(TResult1 extends PromiseLike<infer UResult1> ? UResult1 : TResult1) | (TResult2 extends PromiseLike<infer UResult2> ? UResult2 : TResult2)>;
14041404

14051405
/**
14061406
* Attaches a callback for only the rejection of the Promise.
14071407
* @param onrejected The callback to execute when the Promise is rejected.
14081408
* @returns A Promise for the completion of the callback.
14091409
*/
1410-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
1410+
catch<TResult = never>(onrejected?: ((reason: any) => TResult) | null): Promise<T | (TResult extends PromiseLike<infer UResult> ? UResult : TResult)>;
14111411
}
14121412

14131413
interface ArrayLike<T> {

Diff for: tests/baselines/reference/asyncArrowFunction11_es5.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ class A {
1111
await Promise.resolve();
1212
>await Promise.resolve() : void
1313
>Promise.resolve() : Promise<void>
14-
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
14+
>Promise.resolve : { <T>(value: T): Promise<Awaited<T>>; (): Promise<void>; }
1515
>Promise : PromiseConstructor
16-
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
16+
>resolve : { <T>(value: T): Promise<Awaited<T>>; (): Promise<void>; }
1717

1818
const obj = { ["a"]: () => this }; // computed property name after `await` triggers case
1919
>obj : { ["a"]: () => this; }

Diff for: tests/baselines/reference/asyncArrowFunction5_es2017.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction5_es20
1616
!!! error TS1005: ',' expected.
1717
~~~~~~~
1818
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
19-
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:152:13: 'Promise' was also declared here.
19+
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:74:13: 'Promise' was also declared here.
2020
~
2121
!!! error TS1005: ',' expected.
2222
~~

Diff for: tests/baselines/reference/asyncArrowFunction5_es5.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction5_es5.ts(
1616
!!! error TS1005: ',' expected.
1717
~~~~~~~
1818
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
19-
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:152:13: 'Promise' was also declared here.
19+
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:74:13: 'Promise' was also declared here.
2020
~
2121
!!! error TS1005: ',' expected.
2222
~~

Diff for: tests/baselines/reference/asyncArrowFunction5_es6.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tests/cases/conformance/async/es6/asyncArrowFunction/asyncArrowFunction5_es6.ts(
1616
!!! error TS1005: ',' expected.
1717
~~~~~~~
1818
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
19-
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:152:13: 'Promise' was also declared here.
19+
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:74:13: 'Promise' was also declared here.
2020
~
2121
!!! error TS1005: ',' expected.
2222
~~

Diff for: tests/baselines/reference/asyncArrowFunction9_es2017.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction9_es20
1616
!!! error TS1005: ',' expected.
1717
~~~~~~~
1818
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
19-
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:152:13: 'Promise' was also declared here.
19+
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:74:13: 'Promise' was also declared here.
2020
~
2121
!!! error TS1005: ',' expected.
2222
~~

Diff for: tests/baselines/reference/asyncArrowFunction9_es5.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction9_es5.ts(
1616
!!! error TS1005: ',' expected.
1717
~~~~~~~
1818
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
19-
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:152:13: 'Promise' was also declared here.
19+
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:74:13: 'Promise' was also declared here.
2020
~
2121
!!! error TS1005: ',' expected.
2222
~~

Diff for: tests/baselines/reference/asyncArrowFunction9_es6.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tests/cases/conformance/async/es6/asyncArrowFunction/asyncArrowFunction9_es6.ts(
1616
!!! error TS1005: ',' expected.
1717
~~~~~~~
1818
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'Promise' must be of type 'PromiseConstructor', but here has type 'any'.
19-
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:152:13: 'Promise' was also declared here.
19+
!!! related TS6203 /.ts/lib.es2015.promise.d.ts:74:13: 'Promise' was also declared here.
2020
~
2121
!!! error TS1005: ',' expected.
2222
~~

Diff for: tests/baselines/reference/asyncAwaitNestedClasses_es5.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ class A {
1414
return new Promise((resolve) => { resolve(null); });
1515
>new Promise((resolve) => { resolve(null); }) : Promise<void>
1616
>Promise : PromiseConstructor
17-
>(resolve) => { resolve(null); } : (resolve: (value?: void | PromiseLike<void>) => void) => void
18-
>resolve : (value?: void | PromiseLike<void>) => void
17+
>(resolve) => { resolve(null); } : (resolve: (value?: void) => void) => void
18+
>resolve : (value?: void) => void
1919
>resolve(null) : void
20-
>resolve : (value?: void | PromiseLike<void>) => void
20+
>resolve : (value?: void) => void
2121
>null : null
2222
}
2323
static C = class C {

Diff for: tests/baselines/reference/asyncFunctionDeclaration15_es5.errors.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1
55
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(8,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
66
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(9,23): error TS1055: Type 'PromiseLike' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
77
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(10,23): error TS1055: Type 'typeof Thenable' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
8-
Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
8+
Construct signature return types 'Thenable' and 'PromiseLike<T extends PromiseLike<infer U> ? U : T>' are incompatible.
99
The types returned by 'then(...)' are incompatible between these types.
10-
Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.
10+
Type 'void' is not assignable to type 'PromiseLike<(TResult1 extends PromiseLike<infer UResult1> ? UResult1 : TResult1) | (TResult2 extends PromiseLike<infer UResult2> ? UResult2 : TResult2)>'.
1111
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.
1212
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.
1313

@@ -37,9 +37,9 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1
3737
async function fn6(): Thenable { } // error
3838
~~~~~~~~
3939
!!! error TS1055: Type 'typeof Thenable' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
40-
!!! error TS1055: Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
40+
!!! error TS1055: Construct signature return types 'Thenable' and 'PromiseLike<T extends PromiseLike<infer U> ? U : T>' are incompatible.
4141
!!! error TS1055: The types returned by 'then(...)' are incompatible between these types.
42-
!!! error TS1055: Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.
42+
!!! error TS1055: Type 'void' is not assignable to type 'PromiseLike<(TResult1 extends PromiseLike<infer UResult1> ? UResult1 : TResult1) | (TResult2 extends PromiseLike<infer UResult2> ? UResult2 : TResult2)>'.
4343
async function fn7() { return; } // valid: Promise<void>
4444
async function fn8() { return 1; } // valid: Promise<number>
4545
async function fn9() { return null; } // valid: Promise<any>

Diff for: tests/baselines/reference/asyncFunctionReturnType.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,14 @@ async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TOb
6767
return obj[key];
6868
}
6969

70-
async function fGenericIndexedTypeForPromiseOfKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
70+
async function fGenericIndexedTypeForPromiseOfKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K] extends PromiseLike<infer U> ? U : TObj[K]> {
7171
return Promise.resolve(obj[key]);
7272
}
7373

74-
async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
74+
async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K] extends PromiseLike<infer U> ? U : 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) {

0 commit comments

Comments
 (0)