Skip to content

Commit 4dafbdc

Browse files
committed
[2.x] Add template annotations
1 parent d47bd60 commit 4dafbdc

9 files changed

+158
-25
lines changed

Diff for: .github/workflows/ci.yml

+13
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,16 @@ jobs:
5050
uses: docker://hhvm/hhvm:3.30-lts-latest
5151
with:
5252
args: hhvm vendor/bin/phpunit
53+
54+
static-analysis:
55+
name: PHPStan
56+
runs-on: ubuntu-20.04
57+
continue-on-error: true
58+
steps:
59+
- uses: actions/checkout@v3
60+
- uses: shivammathur/setup-php@v2
61+
with:
62+
php-version: 8.1
63+
- run: composer require phpstan/phpstan
64+
- name: Execute type checking
65+
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.9"
2930
},
3031
"require-dev": {
3132
"phpunit/phpunit": "^9.5 || ^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/FulfilledPromise.php

+13
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44

55
/**
66
* @deprecated 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.
7+
* @template-implements PromiseInterface
8+
* @template-covariant T
79
*/
810
class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
911
{
12+
/**
13+
* @var T
14+
*/
1015
private $value;
1116

17+
/**
18+
* @param T $value
19+
*/
1220
public function __construct($value = null)
1321
{
1422
if ($value instanceof PromiseInterface) {
@@ -18,6 +26,11 @@ public function __construct($value = null)
1826
$this->value = $value;
1927
}
2028

29+
/**
30+
* @template TFulfilled as PromiseInterface<T>|T
31+
* @param (callable(T): TFulfilled)|null $onFulfilled
32+
* @return ($onFulfilled is null ? $this : (TFulfilled is PromiseInterface ? TFulfilled : PromiseInterface<TFulfilled>))
33+
*/
2134
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
2235
{
2336
if (null === $onFulfilled) {

Diff for: src/Promise.php

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

33
namespace React\Promise;
44

5+
/**
6+
* @template-implements PromiseInterface
7+
* @template-covariant T
8+
*/
59
class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
610
{
711
private $canceller;
12+
13+
/**
14+
* @var PromiseInterface<T>
15+
*/
816
private $result;
917

1018
private $handlers = [];
@@ -25,6 +33,11 @@ public function __construct(callable $resolver, callable $canceller = null)
2533
$this->call($cb);
2634
}
2735

36+
/**
37+
* @template TFulfilled as PromiseInterface<T>|T
38+
* @param (callable(T): TFulfilled)|null $onFulfilled
39+
* @return ($onFulfilled is null ? $this : (TFulfilled is PromiseInterface ? TFulfilled : PromiseInterface<TFulfilled>))
40+
*/
2841
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
2942
{
3043
if (null !== $this->result) {

Diff for: src/PromiseInterface.php

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

33
namespace React\Promise;
44

5+
/**
6+
* @template-covariant T
7+
*/
58
interface PromiseInterface
69
{
710
/**
@@ -32,10 +35,9 @@ interface PromiseInterface
3235
* than once.
3336
* 3. `$onProgress` (deprecated) may be called multiple times.
3437
*
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
38+
* @template TFulfilled as PromiseInterface<T>|T
39+
* @param (callable(T): TFulfilled)|null $onFulfilled
40+
* @return ($onFulfilled is null ? $this : (TFulfilled is PromiseInterface ? TFulfilled : PromiseInterface<TFulfilled>))
3941
*/
4042
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
4143
}

Diff for: src/functions.php

+29-18
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
*
1414
* If `$promiseOrValue` is a promise, it will be returned as is.
1515
*
16-
* @param mixed $promiseOrValue
17-
* @return PromiseInterface
16+
* @template-covariant T
17+
* @template TFulfilled as PromiseInterface<T>|T
18+
* @param TFulfilled $promiseOrValue
19+
* @return (TFulfilled is PromiseInterface ? TFulfilled : PromiseInterface<TFulfilled>)
1820
*/
1921
function resolve($promiseOrValue = null)
2022
{
@@ -52,8 +54,10 @@ function resolve($promiseOrValue = null)
5254
* throwing an exception. For example, it allows you to propagate a rejection with
5355
* the value of another promise.
5456
*
55-
* @param mixed $promiseOrValue
56-
* @return PromiseInterface
57+
* @template T is null
58+
* @template R
59+
* @param R $promiseOrValue
60+
* @return PromiseInterface<T>
5761
*/
5862
function reject($promiseOrValue = null)
5963
{
@@ -72,8 +76,9 @@ function reject($promiseOrValue = null)
7276
* will be an array containing the resolution values of each of the items in
7377
* `$promisesOrValues`.
7478
*
75-
* @param array $promisesOrValues
76-
* @return PromiseInterface
79+
* @template T
80+
* @param array<PromiseInterface<T>|T> $promisesOrValues
81+
* @return PromiseInterface<array<T>>
7782
*/
7883
function all($promisesOrValues)
7984
{
@@ -89,8 +94,9 @@ function all($promisesOrValues)
8994
* The returned promise will become **infinitely pending** if `$promisesOrValues`
9095
* contains 0 items.
9196
*
92-
* @param array $promisesOrValues
93-
* @return PromiseInterface
97+
* @template T
98+
* @param array<PromiseInterface<T>|T> $promisesOrValues
99+
* @return PromiseInterface<T>
94100
*/
95101
function race($promisesOrValues)
96102
{
@@ -126,8 +132,9 @@ function race($promisesOrValues)
126132
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
127133
* if `$promisesOrValues` contains 0 items.
128134
*
129-
* @param array $promisesOrValues
130-
* @return PromiseInterface
135+
* @template T
136+
* @param array<PromiseInterface<T>|T> $promisesOrValues
137+
* @return PromiseInterface<T>
131138
*/
132139
function any($promisesOrValues)
133140
{
@@ -151,9 +158,10 @@ function any($promisesOrValues)
151158
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
152159
* if `$promisesOrValues` contains less items than `$howMany`.
153160
*
154-
* @param array $promisesOrValues
161+
* @template T
162+
* @param array<PromiseInterface<T>|T> $promisesOrValues
155163
* @param int $howMany
156-
* @return PromiseInterface
164+
* @return PromiseInterface<array<T>>
157165
*/
158166
function some($promisesOrValues, $howMany)
159167
{
@@ -228,9 +236,11 @@ function some($promisesOrValues, $howMany)
228236
* The map function receives each item as argument, where item is a fully resolved
229237
* value of a promise or value in `$promisesOrValues`.
230238
*
231-
* @param array $promisesOrValues
232-
* @param callable $mapFunc
233-
* @return PromiseInterface
239+
* @template-covariant T
240+
* @template TFulfilled as PromiseInterface<T>|T
241+
* @param array<PromiseInterface<T>|T> $promisesOrValues
242+
* @param callable(T): TFulfilled $mapFunc
243+
* @return PromiseInterface<array<T>>
234244
*/
235245
function map($promisesOrValues, callable $mapFunc)
236246
{
@@ -276,10 +286,11 @@ function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
276286
* promise, *and* `$initialValue` may be a promise or a value for the starting
277287
* value.
278288
*
279-
* @param array $promisesOrValues
280-
* @param callable $reduceFunc
289+
* @template T
290+
* @param array<PromiseInterface<T>|T> $promisesOrValues
291+
* @param callable(T): bool $reduceFunc
281292
* @param mixed $initialValue
282-
* @return PromiseInterface
293+
* @return PromiseInterface<array<T>>
283294
*/
284295
function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
285296
{

Diff for: types/Promises.php

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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\any;
10+
use function React\Promise\race;
11+
use function React\Promise\reject;
12+
use function React\Promise\resolve;
13+
14+
$passThroughBoolFn = static fn (bool $bool): bool => $bool;
15+
$passThroughThrowable = static function (Throwable $t): PromiseInterface {
16+
return reject($t);
17+
};
18+
$stringOrInt = function (): int|string {
19+
return time() % 2 ? 'string' : time();
20+
};
21+
$tosseable = new Exception('Oops I did it again!');
22+
23+
/**
24+
* basic
25+
*/
26+
assertType('React\Promise\PromiseInterface<bool>', resolve(true));
27+
assertType('React\Promise\PromiseInterface<int|string>', resolve($stringOrInt()));
28+
assertType('React\Promise\PromiseInterface<bool>', resolve(resolve(true)));
29+
30+
/**
31+
* chaining
32+
*/
33+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then($passThroughBoolFn));
34+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then()->then($passThroughBoolFn));
35+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then(null)->then($passThroughBoolFn));
36+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then($passThroughBoolFn)->then($passThroughBoolFn));
37+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then($passThroughBoolFn, $passThroughThrowable)->then($passThroughBoolFn));
38+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then(null, $passThroughThrowable)->then($passThroughBoolFn));
39+
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->then()->then(null, $passThroughThrowable)->then($passThroughBoolFn));
40+
41+
/**
42+
* all
43+
*/
44+
assertType('React\Promise\PromiseInterface<array<bool>>', all([resolve(true), resolve(false)]));
45+
assertType('React\Promise\PromiseInterface<array<bool>>', all([resolve(true), false]));
46+
assertType('React\Promise\PromiseInterface<array<bool|int>>', all([true, time()]));
47+
assertType('React\Promise\PromiseInterface<array<bool|int>>', all([resolve(true), resolve(time())]));
48+
assertType('React\Promise\PromiseInterface<array<bool|float>>', all([resolve(true), hrtime()]));
49+
assertType('React\Promise\PromiseInterface<array<bool|int>>', all([true, resolve(time())]));
50+
51+
/**
52+
* any
53+
*/
54+
assertType('React\Promise\PromiseInterface<bool>', any([resolve(true), resolve(false)]));
55+
assertType('React\Promise\PromiseInterface<bool>', any([resolve(true), false]));
56+
assertType('React\Promise\PromiseInterface<bool|int>', any([true, time()]));
57+
assertType('React\Promise\PromiseInterface<bool|int>', any([resolve(true), resolve(time())]));
58+
assertType('React\Promise\PromiseInterface<bool|float>', any([resolve(true), hrtime()]));
59+
assertType('React\Promise\PromiseInterface<bool|int>', any([true, resolve(time())]));
60+
61+
/**
62+
* race
63+
*/
64+
assertType('React\Promise\PromiseInterface<bool>', race([resolve(true), resolve(false)]));
65+
assertType('React\Promise\PromiseInterface<bool>', race([resolve(true), false]));
66+
assertType('React\Promise\PromiseInterface<bool|int>', race([true, time()]));
67+
assertType('React\Promise\PromiseInterface<bool|int>', race([resolve(true), resolve(time())]));
68+
assertType('React\Promise\PromiseInterface<bool|float>', race([resolve(true), hrtime()]));
69+
assertType('React\Promise\PromiseInterface<bool|int>', race([true, resolve(time())]));
70+
71+
/**
72+
* direct class access (deprecated!!!)
73+
*/
74+
assertType('React\Promise\FulfilledPromise<bool>', new FulfilledPromise(true));
75+
assertType('React\Promise\PromiseInterface<bool>', (new FulfilledPromise(true))->then($passThroughBoolFn));

0 commit comments

Comments
 (0)