Skip to content

Commit 45dae63

Browse files
committed
Add template annotations to async and await
These annotations will aid static analyses like PHPStan and Psalm to enhance type-safety for this project and projects depending on it
1 parent b9641ac commit 45dae63

File tree

9 files changed

+86
-20
lines changed

9 files changed

+86
-20
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
name: PHPStan (PHP ${{ matrix.php }})
2828
runs-on: ubuntu-22.04
2929
strategy:
30+
fail-fast: false
3031
matrix:
3132
php:
3233
- 8.2

src/FiberMap.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ final class FiberMap
1212
/** @var array<int,bool> */
1313
private static array $status = [];
1414

15-
/** @var array<int,PromiseInterface> */
15+
/** @var array<int,PromiseInterface<mixed>> */
1616
private static array $map = [];
1717

1818
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
@@ -27,19 +27,28 @@ public static function cancel(\Fiber $fiber): void
2727
self::$status[\spl_object_id($fiber)] = true;
2828
}
2929

30-
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
30+
/**
31+
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
32+
* @param PromiseInterface<mixed> $promise
33+
*/
3134
public static function setPromise(\Fiber $fiber, PromiseInterface $promise): void
3235
{
3336
self::$map[\spl_object_id($fiber)] = $promise;
3437
}
3538

36-
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
39+
/**
40+
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
41+
* @param PromiseInterface<mixed> $promise
42+
*/
3743
public static function unsetPromise(\Fiber $fiber, PromiseInterface $promise): void
3844
{
3945
unset(self::$map[\spl_object_id($fiber)]);
4046
}
4147

42-
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
48+
/**
49+
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
50+
* @return ?PromiseInterface<mixed>
51+
*/
4352
public static function getPromise(\Fiber $fiber): ?PromiseInterface
4453
{
4554
return self::$map[\spl_object_id($fiber)] ?? null;

src/functions.php

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,11 @@
176176
* await($promise);
177177
* ```
178178
*
179-
* @param callable $function
180-
* @return callable(mixed ...): PromiseInterface<mixed>
179+
* @template T
180+
* @template TFulfilled as PromiseInterface<T>|T
181+
* @template A
182+
* @param (callable(): TFulfilled)|(callable(A): TFulfilled) $function
183+
* @return callable(mixed ...$args): PromiseInterface<T>
181184
* @since 4.0.0
182185
* @see coroutine()
183186
*/
@@ -268,8 +271,9 @@ function async(callable $function): callable
268271
* }
269272
* ```
270273
*
271-
* @param PromiseInterface $promise
272-
* @return mixed returns whatever the promise resolves to
274+
* @template T
275+
* @param PromiseInterface<T> $promise
276+
* @return T
273277
* @throws \Exception when the promise is rejected with an `Exception`
274278
* @throws \Throwable when the promise is rejected with a `Throwable`
275279
* @throws \UnexpectedValueException when the promise is rejected with an unexpected value (Promise API v1 or v2 only)
@@ -279,6 +283,10 @@ function await(PromiseInterface $promise): mixed
279283
$fiber = null;
280284
$resolved = false;
281285
$rejected = false;
286+
287+
/**
288+
* @var T $resolvedValue
289+
*/
282290
$resolvedValue = null;
283291
$rejectedThrowable = null;
284292
$lowLevelFiber = \Fiber::getCurrent();
@@ -292,6 +300,9 @@ function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber, $lowLevelFibe
292300
/** @var ?\Fiber<mixed,mixed,mixed,mixed> $fiber */
293301
if ($fiber === null) {
294302
$resolved = true;
303+
/**
304+
* @var T $resolvedValue
305+
*/
295306
$resolvedValue = $value;
296307
return;
297308
}
@@ -354,7 +365,11 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowL
354365

355366
$fiber = FiberFactory::create();
356367

357-
return $fiber->suspend();
368+
/**
369+
* @var T $result
370+
*/
371+
$result = $fiber->suspend();
372+
return $result;
358373
}
359374

360375
/**
@@ -609,9 +624,9 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface
609624
return resolve($generator);
610625
}
611626

627+
/** @var ?PromiseInterface<mixed> $promise */
612628
$promise = null;
613629
$deferred = new Deferred(function () use (&$promise) {
614-
/** @var ?PromiseInterface $promise */
615630
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
616631
$promise->cancel();
617632
}
@@ -660,12 +675,13 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface
660675
}
661676

662677
/**
663-
* @param iterable<callable():PromiseInterface<mixed>> $tasks
664-
* @return PromiseInterface<array<mixed>>
678+
* @template T
679+
* @param iterable<callable():PromiseInterface<T>> $tasks
680+
* @return PromiseInterface<array<T>>
665681
*/
666682
function parallel(iterable $tasks): PromiseInterface
667683
{
668-
/** @var array<int,PromiseInterface> $pending */
684+
/** @var array<int,PromiseInterface<T>> $pending */
669685
$pending = [];
670686
$deferred = new Deferred(function () use (&$pending) {
671687
foreach ($pending as $promise) {
@@ -720,14 +736,15 @@ function parallel(iterable $tasks): PromiseInterface
720736
}
721737

722738
/**
723-
* @param iterable<callable():PromiseInterface<mixed>> $tasks
724-
* @return PromiseInterface<array<mixed>>
739+
* @template T
740+
* @param iterable<callable():PromiseInterface<T>> $tasks
741+
* @return PromiseInterface<array<T>>
725742
*/
726743
function series(iterable $tasks): PromiseInterface
727744
{
728745
$pending = null;
729746
$deferred = new Deferred(function () use (&$pending) {
730-
/** @var ?PromiseInterface $pending */
747+
/** @var ?PromiseInterface<T> $pending */
731748
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
732749
$pending->cancel();
733750
}
@@ -774,14 +791,15 @@ function series(iterable $tasks): PromiseInterface
774791
}
775792

776793
/**
777-
* @param iterable<(callable():PromiseInterface<mixed>)|(callable(mixed):PromiseInterface<mixed>)> $tasks
778-
* @return PromiseInterface<mixed>
794+
* @template T
795+
* @param iterable<(callable():PromiseInterface<T>)|(callable(mixed):PromiseInterface<T>)> $tasks
796+
* @return PromiseInterface<T>
779797
*/
780798
function waterfall(iterable $tasks): PromiseInterface
781799
{
782800
$pending = null;
783801
$deferred = new Deferred(function () use (&$pending) {
784-
/** @var ?PromiseInterface $pending */
802+
/** @var ?PromiseInterface<T> $pending */
785803
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
786804
$pending->cancel();
787805
}

tests/AwaitTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ public function testRejectedPromisesShouldBeDetached(callable $await): void
413413
})());
414414
}
415415

416-
/** @return iterable<string,list<callable(PromiseInterface): mixed>> */
416+
/** @return iterable<string,list<callable(PromiseInterface<mixed>): mixed>> */
417417
public function provideAwaiters(): iterable
418418
{
419419
yield 'await' => [static fn (React\Promise\PromiseInterface $promise): mixed => React\Async\await($promise)];

tests/ParallelTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class ParallelTest extends TestCase
1212
{
1313
public function testParallelWithoutTasks(): void
1414
{
15+
/**
16+
* @var array<callable(): React\Promise\PromiseInterface<mixed>> $tasks
17+
*/
1518
$tasks = array();
1619

1720
$promise = React\Async\parallel($tasks);

tests/SeriesTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class SeriesTest extends TestCase
1212
{
1313
public function testSeriesWithoutTasks(): void
1414
{
15+
/**
16+
* @var array<callable(): React\Promise\PromiseInterface<mixed>> $tasks
17+
*/
1518
$tasks = array();
1619

1720
$promise = React\Async\series($tasks);
@@ -151,6 +154,9 @@ public function testSeriesWithErrorFromInfiniteIteratorAggregateReturnsPromiseRe
151154
$tasks = new class() implements \IteratorAggregate {
152155
public int $called = 0;
153156

157+
/**
158+
* @return \Iterator<callable(): React\Promise\PromiseInterface<mixed>>
159+
*/
154160
public function getIterator(): \Iterator
155161
{
156162
while (true) { // @phpstan-ignore-line

tests/WaterfallTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class WaterfallTest extends TestCase
1212
{
1313
public function testWaterfallWithoutTasks(): void
1414
{
15+
/**
16+
* @var array<callable(): React\Promise\PromiseInterface<mixed>> $tasks
17+
*/
1518
$tasks = array();
1619

1720
$promise = React\Async\waterfall($tasks);
@@ -165,6 +168,9 @@ public function testWaterfallWithErrorFromInfiniteIteratorAggregateReturnsPromis
165168
$tasks = new class() implements \IteratorAggregate {
166169
public int $called = 0;
167170

171+
/**
172+
* @return \Iterator<callable(): React\Promise\PromiseInterface<mixed>>
173+
*/
168174
public function getIterator(): \Iterator
169175
{
170176
while (true) { // @phpstan-ignore-line

tests/types/async.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
use React\Promise\PromiseInterface;
4+
use function PHPStan\Testing\assertType;
5+
use function React\Async\async;
6+
use function React\Async\await;
7+
use function React\Promise\resolve;
8+
9+
assertType('React\Promise\PromiseInterface<bool>', resolve(true));
10+
assertType('React\Promise\PromiseInterface<bool>', async(static fn (): PromiseInterface => resolve(true))());
11+
assertType('React\Promise\PromiseInterface<bool>', async(static fn (): bool => await(resolve(true)))());

tests/types/await.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
use React\Promise\PromiseInterface;
4+
use function PHPStan\Testing\assertType;
5+
use function React\Async\async;
6+
use function React\Async\await;
7+
use function React\Promise\resolve;
8+
9+
assertType('bool', await(resolve(true)));
10+
assertType('bool', await(async(static fn (): bool => true)()));
11+
assertType('bool', await(async(static fn (): PromiseInterface => resolve(true))()));
12+
assertType('bool', await(async(static fn (): bool => await(resolve(true)))()));

0 commit comments

Comments
 (0)