Skip to content

Commit b87eda7

Browse files
committed
[2.x] Add template annotations
1 parent 77aa876 commit b87eda7

File tree

7 files changed

+98
-23
lines changed

7 files changed

+98
-23
lines changed

Diff for: .github/workflows/ci.yml

+13
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,16 @@ jobs:
4545
- run: composer self-update --2.2 # downgrade Composer for HHVM
4646
- run: hhvm $(which composer) install
4747
- run: hhvm vendor/bin/phpunit
48+
49+
static-analysis:
50+
name: PHPStan
51+
runs-on: ubuntu-20.04
52+
continue-on-error: true
53+
steps:
54+
- uses: actions/checkout@v3
55+
- uses: shivammathur/setup-php@v2
56+
with:
57+
php-version: 8.1
58+
- run: composer require phpstan/phpstan
59+
- name: Execute type checking
60+
run: vendor/bin/phpstan --configuration="phpstan.types.neon.dist"

Diff for: composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
}
2626
],
2727
"require": {
28-
"php": ">=5.4.0"
28+
"php": ">=5.4.0",
29+
"phpstan/phpstan": "^1.7"
2930
},
3031
"require-dev": {
3132
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36"

Diff for: phpstan.types.neon.dist

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
parameters:
2+
paths:
3+
- types
4+
level: max

Diff for: src/ExtendedPromiseInterface.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ public function otherwise(callable $onRejected);
7878
* ->always('cleanup');
7979
* ```
8080
*
81-
* @param callable $onFulfilledOrRejected
82-
* @return ExtendedPromiseInterface
81+
* @template TReturn of mixed
82+
* @param callable(T): TReturn $onFulfilledOrRejected
83+
* @return (TReturn is ExtendedPromiseInterface ? TReturn : ExtendedPromiseInterface<TReturn>)
8384
*/
8485
public function always(callable $onFulfilledOrRejected);
8586

Diff for: src/PromiseInterface.php

+13-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
namespace React\Promise;
44

5+
/**
6+
* @template T
7+
* @template R
8+
*/
59
interface PromiseInterface
610
{
711
/**
@@ -32,10 +36,15 @@ interface PromiseInterface
3236
* than once.
3337
* 3. `$onProgress` (deprecated) may be called multiple times.
3438
*
35-
* @param callable|null $onFulfilled
36-
* @param callable|null $onRejected
37-
* @param callable|null $onProgress This argument is deprecated and should not be used anymore.
38-
* @return PromiseInterface
39+
* @template TFulfilled of mixed
40+
* @template TRejected of mixed
41+
* @param (callable(T): (PromiseInterface<TFulfilled>|TFulfilled))|null $onFulfilled
42+
* @param (callable(R): (PromiseInterface<TRejected>|TRejected))|null $onRejected
43+
* @return PromiseInterface<(
44+
* $onFulfilled is not null
45+
* ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled)
46+
* : ($onRejected is not null ? TRejected : R)
47+
* )>
3948
*/
4049
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
4150
}

Diff for: src/functions.php

+23-16
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
*
1414
* If `$promiseOrValue` is a promise, it will be returned as is.
1515
*
16-
* @param mixed $promiseOrValue
17-
* @return PromiseInterface
16+
* @template T
17+
* @param T $promiseOrValue
18+
* @return PromiseInterface<T>
1819
*/
1920
function resolve($promiseOrValue = null)
2021
{
@@ -72,8 +73,9 @@ function reject($promiseOrValue = null)
7273
* will be an array containing the resolution values of each of the items in
7374
* `$promisesOrValues`.
7475
*
75-
* @param array $promisesOrValues
76-
* @return PromiseInterface
76+
* @template T
77+
* @param array<PromiseInterface<T>|T> $promisesOrValues
78+
* @return PromiseInterface<array<T>>
7779
*/
7880
function all($promisesOrValues)
7981
{
@@ -89,8 +91,9 @@ function all($promisesOrValues)
8991
* The returned promise will become **infinitely pending** if `$promisesOrValues`
9092
* contains 0 items.
9193
*
92-
* @param array $promisesOrValues
93-
* @return PromiseInterface
94+
* @template T
95+
* @param array<PromiseInterface<T>|T> $promisesOrValues
96+
* @return PromiseInterface<T>
9497
*/
9598
function race($promisesOrValues)
9699
{
@@ -126,8 +129,9 @@ function race($promisesOrValues)
126129
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
127130
* if `$promisesOrValues` contains 0 items.
128131
*
129-
* @param array $promisesOrValues
130-
* @return PromiseInterface
132+
* @template T
133+
* @param array<PromiseInterface<T>|T> $promisesOrValues
134+
* @return PromiseInterface<array<T>>
131135
*/
132136
function any($promisesOrValues)
133137
{
@@ -151,9 +155,10 @@ function any($promisesOrValues)
151155
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
152156
* if `$promisesOrValues` contains less items than `$howMany`.
153157
*
154-
* @param array $promisesOrValues
158+
* @template T
159+
* @param array<PromiseInterface<T>|T> $promisesOrValues
155160
* @param int $howMany
156-
* @return PromiseInterface
161+
* @return PromiseInterface<array<T>>
157162
*/
158163
function some($promisesOrValues, $howMany)
159164
{
@@ -228,9 +233,10 @@ function some($promisesOrValues, $howMany)
228233
* The map function receives each item as argument, where item is a fully resolved
229234
* value of a promise or value in `$promisesOrValues`.
230235
*
231-
* @param array $promisesOrValues
232-
* @param callable $mapFunc
233-
* @return PromiseInterface
236+
* @template T
237+
* @param array<PromiseInterface<T>|T> $promisesOrValues
238+
* @param callable(T): T $mapFunc
239+
* @return PromiseInterface<array<T>>
234240
*/
235241
function map($promisesOrValues, callable $mapFunc)
236242
{
@@ -276,10 +282,11 @@ function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
276282
* promise, *and* `$initialValue` may be a promise or a value for the starting
277283
* value.
278284
*
279-
* @param array $promisesOrValues
280-
* @param callable $reduceFunc
285+
* @template T
286+
* @param array<PromiseInterface<T>|T> $promisesOrValues
287+
* @param callable(T): bool $reduceFunc
281288
* @param mixed $initialValue
282-
* @return PromiseInterface
289+
* @return PromiseInterface<array<T>>
283290
*/
284291
function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
285292
{

Diff for: types/Promises.php

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
use React\Promise\FulfilledPromise;
4+
use React\Promise\PromiseInterface;
5+
use Throwable;
6+
7+
use function PHPStan\Testing\assertType;
8+
use function React\Promise\all;
9+
use function React\Promise\race;
10+
use function React\Promise\reject;
11+
use function React\Promise\resolve;
12+
13+
$passThroughBoolFn = static fn (bool $bool): bool => $bool;
14+
$passThroughThrowable = static function (Throwable $t): PromiseInterface {
15+
return reject($t);
16+
};
17+
$tosseable = new Exception('Oops I did it again!');
18+
19+
assertType('React\Promise\PromiseInterface<bool>', resolve(true));
20+
assertType('React\Promise\PromiseInterface<array<bool>>', all([resolve(true), resolve(false)]));
21+
assertType('React\Promise\PromiseInterface<array<bool>>', all([resolve(true), false]));
22+
assertType('React\Promise\PromiseInterface<array<bool|int>>', all([resolve(true), resolve(time())]));
23+
assertType('React\Promise\PromiseInterface<array<bool|int>>', all([resolve(true), time()]));
24+
assertType('React\Promise\PromiseInterface<array<bool|int>>', all([true, resolve(time())]));
25+
assertType('React\Promise\PromiseInterface<bool>', race([resolve(true), resolve(true)]));
26+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then($passThroughBoolFn));
27+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then()->then($passThroughBoolFn));
28+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then(null)->then($passThroughBoolFn));
29+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then($passThroughBoolFn)->then($passThroughBoolFn));
30+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then($passThroughBoolFn, $passThroughThrowable)->then($passThroughBoolFn));
31+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then(null, $passThroughThrowable)->then($passThroughBoolFn));
32+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then()->then(null, $passThroughThrowable)->then($passThroughBoolFn));
33+
assertType('React\Promise\FulfilledPromise<bool>', new FulfilledPromise(true));
34+
assertType('React\Promise\PromiseInterface<bool>', (new FulfilledPromise(true))->then($passThroughBoolFn));
35+
36+
//assertType('React\Promise\PromiseInterface<Throwable>', reject($tosseable));
37+
//assertType('React\Promise\PromiseInterface<Throwable>', reject($tosseable)->then($passThroughBoolFn, $passThroughThrowable));
38+
//assertType('React\Promise\PromiseInterface<Throwable>', reject($tosseable)->then()->then($passThroughBoolFn, $passThroughThrowable));
39+
//assertType('React\Promise\PromiseInterface<Throwable>', reject($tosseable)->then(null, $passThroughThrowable));
40+
//assertType('React\Promise\PromiseInterface<Throwable>', reject($tosseable)->then()->then(null, $passThroughThrowable));

0 commit comments

Comments
 (0)