Skip to content

Commit 82b32eb

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

9 files changed

+116
-23
lines changed

.github/workflows/ci.yml

Lines changed: 13 additions & 0 deletions
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"

composer.json

Lines changed: 2 additions & 1 deletion
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"

phpstan.types.neon.dist

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

src/ExtendedPromiseInterface.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace React\Promise;
44

5+
/** @template T */
56
interface ExtendedPromiseInterface extends PromiseInterface
67
{
78
/**
@@ -78,8 +79,9 @@ public function otherwise(callable $onRejected);
7879
* ->always('cleanup');
7980
* ```
8081
*
81-
* @param callable $onFulfilledOrRejected
82-
* @return ExtendedPromiseInterface
82+
* @template TReturn of mixed
83+
* @param callable(T): TReturn $onFulfilledOrRejected
84+
* @return (TReturn is ExtendedPromiseInterface ? TReturn : ExtendedPromiseInterface<TReturn>)
8385
*/
8486
public function always(callable $onFulfilledOrRejected);
8587

src/FulfilledPromise.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44

55
/**
66
* @deprecated 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.
7+
* @template T
8+
* @template-implements PromiseInterface<T>
79
*/
810
class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
911
{
1012
private $value;
1113

14+
/**
15+
* @param T $value
16+
*/
1217
public function __construct($value = null)
1318
{
1419
if ($value instanceof PromiseInterface) {
@@ -18,6 +23,9 @@ public function __construct($value = null)
1823
$this->value = $value;
1924
}
2025

26+
/**
27+
* @inheritDoc
28+
*/
2129
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
2230
{
2331
if (null === $onFulfilled) {

src/Promise.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22

33
namespace React\Promise;
44

5+
/**
6+
* @template F
7+
* @template R
8+
* @template-implements PromiseInterface<F|R>
9+
*/
510
class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
611
{
712
private $canceller;
13+
/** @var F */
814
private $result;
915

1016
private $handlers = [];
@@ -25,6 +31,9 @@ public function __construct(callable $resolver, callable $canceller = null)
2531
$this->call($cb);
2632
}
2733

34+
/**
35+
* @inheritDoc
36+
*/
2837
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
2938
{
3039
if (null !== $this->result) {

src/PromiseInterface.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
namespace React\Promise;
44

5+
/**
6+
* @template F
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(F): (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 : F)
47+
* )>
3948
*/
4049
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
4150
}

src/functions.php

Lines changed: 23 additions & 16 deletions
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<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 $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
{

types/Promises.php

Lines changed: 40 additions & 0 deletions
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)