Skip to content

Commit b524bca

Browse files
committed
Improve type definitions and update to PHPStan level max
1 parent 0fdd6a4 commit b524bca

14 files changed

+174
-134
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ To run the test suite, go to the project root and run:
659659
vendor/bin/phpunit
660660
```
661661

662-
On top of this, we use PHPStan on level 3 to ensure type safety across the project:
662+
On top of this, we use PHPStan on max level to ensure type safety across the project:
663663

664664
```bash
665665
vendor/bin/phpstan

phpstan.neon.dist

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
parameters:
2-
level: 3
2+
level: max
33

44
paths:
55
- src/

src/FiberMap.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,43 @@
99
*/
1010
final class FiberMap
1111
{
12+
/** @var array<int,bool> */
1213
private static array $status = [];
13-
private static array $map = [];
1414

15+
/** @var array<int,PromiseInterface> */
16+
private static array $map = [];
17+
18+
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
1519
public static function register(\Fiber $fiber): void
1620
{
1721
self::$status[\spl_object_id($fiber)] = false;
18-
self::$map[\spl_object_id($fiber)] = [];
1922
}
2023

24+
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
2125
public static function cancel(\Fiber $fiber): void
2226
{
2327
self::$status[\spl_object_id($fiber)] = true;
2428
}
2529

30+
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
2631
public static function setPromise(\Fiber $fiber, PromiseInterface $promise): void
2732
{
2833
self::$map[\spl_object_id($fiber)] = $promise;
2934
}
3035

36+
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
3137
public static function unsetPromise(\Fiber $fiber, PromiseInterface $promise): void
3238
{
3339
unset(self::$map[\spl_object_id($fiber)]);
3440
}
3541

42+
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
3643
public static function getPromise(\Fiber $fiber): ?PromiseInterface
3744
{
3845
return self::$map[\spl_object_id($fiber)] ?? null;
3946
}
4047

48+
/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
4149
public static function unregister(\Fiber $fiber): void
4250
{
4351
unset(self::$status[\spl_object_id($fiber)], self::$map[\spl_object_id($fiber)]);

src/SimpleFiber.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99
*/
1010
final class SimpleFiber implements FiberInterface
1111
{
12+
/** @var ?\Fiber<void,void,void,callable(): mixed> */
1213
private static ?\Fiber $scheduler = null;
14+
1315
private static ?\Closure $suspend = null;
16+
17+
/** @var ?\Fiber<mixed,mixed,mixed,mixed> */
1418
private ?\Fiber $fiber = null;
1519

1620
public function __construct()
@@ -57,13 +61,17 @@ public function suspend(): mixed
5761
self::$scheduler = new \Fiber(static fn() => Loop::run());
5862
// Run event loop to completion on shutdown.
5963
\register_shutdown_function(static function (): void {
64+
assert(self::$scheduler instanceof \Fiber);
6065
if (self::$scheduler->isSuspended()) {
6166
self::$scheduler->resume();
6267
}
6368
});
6469
}
6570

66-
return (self::$scheduler->isStarted() ? self::$scheduler->resume() : self::$scheduler->start())();
71+
$ret = (self::$scheduler->isStarted() ? self::$scheduler->resume() : self::$scheduler->start());
72+
assert(\is_callable($ret));
73+
74+
return $ret();
6775
}
6876

6977
return \Fiber::suspend();

src/functions.php

+20-5
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@
176176
* await($promise);
177177
* ```
178178
*
179-
* @param callable(mixed ...$args):mixed $function
180-
* @return callable(mixed ...$args): PromiseInterface<mixed>
179+
* @param callable $function
180+
* @return callable(mixed ...): PromiseInterface<mixed>
181181
* @since 4.0.0
182182
* @see coroutine()
183183
*/
@@ -192,6 +192,7 @@ function async(callable $function): callable
192192
} catch (\Throwable $exception) {
193193
$reject($exception);
194194
} finally {
195+
assert($fiber instanceof \Fiber);
195196
FiberMap::unregister($fiber);
196197
}
197198
});
@@ -200,6 +201,7 @@ function async(callable $function): callable
200201

201202
$fiber->start();
202203
}, function () use (&$fiber): void {
204+
assert($fiber instanceof \Fiber);
203205
FiberMap::cancel($fiber);
204206
$promise = FiberMap::getPromise($fiber);
205207
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
@@ -287,6 +289,7 @@ function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber, $lowLevelFibe
287289
FiberMap::unsetPromise($lowLevelFiber, $promise);
288290
}
289291

292+
/** @var ?\Fiber<mixed,mixed,mixed,mixed> $fiber */
290293
if ($fiber === null) {
291294
$resolved = true;
292295
$resolvedValue = $value;
@@ -309,6 +312,7 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowL
309312
// what a lovely piece of code!
310313
$r = new \ReflectionProperty('Exception', 'trace');
311314
$trace = $r->getValue($throwable);
315+
assert(\is_array($trace));
312316

313317
// Exception trace arguments only available when zend.exception_ignore_args is not set
314318
// @codeCoverageIgnoreStart
@@ -340,6 +344,7 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowL
340344
}
341345

342346
if ($rejected) {
347+
assert($rejectedThrowable instanceof \Throwable);
343348
throw $rejectedThrowable;
344349
}
345350

@@ -587,7 +592,7 @@ function delay(float $seconds): void
587592
* });
588593
* ```
589594
*
590-
* @param callable(mixed ...$args):\Generator<mixed,PromiseInterface,mixed,mixed> $function
595+
* @param callable(mixed ...$args):(\Generator<mixed,PromiseInterface,mixed,mixed>|mixed) $function
591596
* @param mixed ...$args Optional list of additional arguments that will be passed to the given `$function` as is
592597
* @return PromiseInterface<mixed>
593598
* @since 3.0.0
@@ -606,6 +611,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface
606611

607612
$promise = null;
608613
$deferred = new Deferred(function () use (&$promise) {
614+
/** @var ?PromiseInterface $promise */
609615
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
610616
$promise->cancel();
611617
}
@@ -626,6 +632,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface
626632
return;
627633
}
628634

635+
/** @var mixed $promise */
629636
$promise = $generator->current();
630637
if (!$promise instanceof PromiseInterface) {
631638
$next = null;
@@ -635,6 +642,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface
635642
return;
636643
}
637644

645+
assert($next instanceof \Closure);
638646
$promise->then(function ($value) use ($generator, $next) {
639647
$generator->send($value);
640648
$next();
@@ -657,6 +665,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface
657665
*/
658666
function parallel(iterable $tasks): PromiseInterface
659667
{
668+
/** @var array<int,PromiseInterface> $pending */
660669
$pending = [];
661670
$deferred = new Deferred(function () use (&$pending) {
662671
foreach ($pending as $promise) {
@@ -718,6 +727,7 @@ function series(iterable $tasks): PromiseInterface
718727
{
719728
$pending = null;
720729
$deferred = new Deferred(function () use (&$pending) {
730+
/** @var ?PromiseInterface $pending */
721731
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
722732
$pending->cancel();
723733
}
@@ -732,7 +742,7 @@ function series(iterable $tasks): PromiseInterface
732742

733743
$taskCallback = function ($result) use (&$results, &$next) {
734744
$results[] = $result;
735-
assert($next instanceof \Closure);
745+
/** @var \Closure $next */
736746
$next();
737747
};
738748

@@ -746,9 +756,11 @@ function series(iterable $tasks): PromiseInterface
746756
$task = $tasks->current();
747757
$tasks->next();
748758
} else {
759+
assert(\is_array($tasks));
749760
$task = \array_shift($tasks);
750761
}
751762

763+
assert(\is_callable($task));
752764
$promise = \call_user_func($task);
753765
assert($promise instanceof PromiseInterface);
754766
$pending = $promise;
@@ -762,13 +774,14 @@ function series(iterable $tasks): PromiseInterface
762774
}
763775

764776
/**
765-
* @param iterable<callable(mixed=):PromiseInterface<mixed>> $tasks
777+
* @param iterable<(callable():PromiseInterface<mixed>)|(callable(mixed):PromiseInterface<mixed>)> $tasks
766778
* @return PromiseInterface<mixed>
767779
*/
768780
function waterfall(iterable $tasks): PromiseInterface
769781
{
770782
$pending = null;
771783
$deferred = new Deferred(function () use (&$pending) {
784+
/** @var ?PromiseInterface $pending */
772785
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
773786
$pending->cancel();
774787
}
@@ -791,9 +804,11 @@ function waterfall(iterable $tasks): PromiseInterface
791804
$task = $tasks->current();
792805
$tasks->next();
793806
} else {
807+
assert(\is_array($tasks));
794808
$task = \array_shift($tasks);
795809
}
796810

811+
assert(\is_callable($task));
797812
$promise = \call_user_func_array($task, func_get_args());
798813
assert($promise instanceof PromiseInterface);
799814
$pending = $promise;

tests/AsyncTest.php

+17-16
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
class AsyncTest extends TestCase
1616
{
17-
public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsValue()
17+
public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsValue(): void
1818
{
1919
$promise = async(function () {
2020
return 42;
@@ -28,7 +28,7 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsV
2828
$this->assertEquals(42, $value);
2929
}
3030

31-
public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsPromiseThatFulfillsWithValue()
31+
public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsPromiseThatFulfillsWithValue(): void
3232
{
3333
$promise = async(function () {
3434
return resolve(42);
@@ -42,7 +42,7 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsP
4242
$this->assertEquals(42, $value);
4343
}
4444

45-
public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrows()
45+
public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrows(): void
4646
{
4747
$promise = async(function () {
4848
throw new \RuntimeException('Foo', 42);
@@ -59,7 +59,7 @@ public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrow
5959
$this->assertEquals(42, $exception->getCode());
6060
}
6161

62-
public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackReturnsPromiseThatRejectsWithException()
62+
public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackReturnsPromiseThatRejectsWithException(): void
6363
{
6464
$promise = async(function () {
6565
return reject(new \RuntimeException('Foo', 42));
@@ -76,7 +76,7 @@ public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackRetur
7676
$this->assertEquals(42, $exception->getCode());
7777
}
7878

79-
public function testAsyncReturnsPendingPromiseWhenCallbackReturnsPendingPromise()
79+
public function testAsyncReturnsPendingPromiseWhenCallbackReturnsPendingPromise(): void
8080
{
8181
$promise = async(function () {
8282
return new Promise(function () { });
@@ -85,7 +85,7 @@ public function testAsyncReturnsPendingPromiseWhenCallbackReturnsPendingPromise(
8585
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
8686
}
8787

88-
public function testAsyncWithAwaitReturnsReturnsPromiseFulfilledWithValueImmediatelyWhenPromiseIsFulfilled()
88+
public function testAsyncWithAwaitReturnsReturnsPromiseFulfilledWithValueImmediatelyWhenPromiseIsFulfilled(): void
8989
{
9090
$deferred = new Deferred();
9191

@@ -105,7 +105,7 @@ public function testAsyncWithAwaitReturnsReturnsPromiseFulfilledWithValueImmedia
105105
$this->assertEquals(42, $return);
106106
}
107107

108-
public function testAsyncWithAwaitReturnsPromiseRejectedWithExceptionImmediatelyWhenPromiseIsRejected()
108+
public function testAsyncWithAwaitReturnsPromiseRejectedWithExceptionImmediatelyWhenPromiseIsRejected(): void
109109
{
110110
$deferred = new Deferred();
111111

@@ -122,13 +122,13 @@ public function testAsyncWithAwaitReturnsPromiseRejectedWithExceptionImmediately
122122

123123
$deferred->reject(new \RuntimeException('Test', 42));
124124

125+
/** @var \RuntimeException $exception */
125126
$this->assertInstanceof(\RuntimeException::class, $exception);
126-
assert($exception instanceof \RuntimeException);
127127
$this->assertEquals('Test', $exception->getMessage());
128128
$this->assertEquals(42, $exception->getCode());
129129
}
130130

131-
public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsAfterAwaitingPromise()
131+
public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsAfterAwaitingPromise(): void
132132
{
133133
$promise = async(function () {
134134
$promise = new Promise(function ($resolve) {
@@ -143,23 +143,23 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsA
143143
$this->assertEquals(42, $value);
144144
}
145145

146-
public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrowsAfterAwaitingPromise()
146+
public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrowsAfterAwaitingPromise(): void
147147
{
148148
$promise = async(function () {
149149
$promise = new Promise(function ($_, $reject) {
150150
Loop::addTimer(0.001, fn () => $reject(new \RuntimeException('Foo', 42)));
151151
});
152152

153153
return await($promise);
154-
})();
154+
})(42);
155155

156156
$this->expectException(\RuntimeException::class);
157157
$this->expectExceptionMessage('Foo');
158158
$this->expectExceptionCode(42);
159159
await($promise);
160160
}
161161

162-
public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsAfterAwaitingTwoConcurrentPromises()
162+
public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsAfterAwaitingTwoConcurrentPromises(): void
163163
{
164164
$promise1 = async(function () {
165165
$promise = new Promise(function ($resolve) {
@@ -174,6 +174,7 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsA
174174
Loop::addTimer(0.11, fn () => $resolve($theAnswerToLifeTheUniverseAndEverything));
175175
});
176176

177+
/** @var int */
177178
return await($promise);
178179
})(42);
179180

@@ -186,7 +187,7 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsA
186187
$this->assertLessThan(0.12, $time);
187188
}
188189

189-
public function testCancelAsyncWillReturnRejectedPromiseWhenCancellingPendingPromiseRejects()
190+
public function testCancelAsyncWillReturnRejectedPromiseWhenCancellingPendingPromiseRejects(): void
190191
{
191192
$promise = async(function () {
192193
await(new Promise(function () { }, function () {
@@ -200,7 +201,7 @@ public function testCancelAsyncWillReturnRejectedPromiseWhenCancellingPendingPro
200201
$promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Operation cancelled')));
201202
}
202203

203-
public function testCancelAsyncWillReturnFulfilledPromiseWhenCancellingPendingPromiseRejectsInsideCatchThatReturnsValue()
204+
public function testCancelAsyncWillReturnFulfilledPromiseWhenCancellingPendingPromiseRejectsInsideCatchThatReturnsValue(): void
204205
{
205206
$promise = async(function () {
206207
try {
@@ -218,7 +219,7 @@ public function testCancelAsyncWillReturnFulfilledPromiseWhenCancellingPendingPr
218219
$promise->then($this->expectCallableOnceWith(42));
219220
}
220221

221-
public function testCancelAsycWillReturnPendigPromiseWhenCancellingFirstPromiseRejectsInsideCatchThatAwaitsSecondPromise()
222+
public function testCancelAsycWillReturnPendigPromiseWhenCancellingFirstPromiseRejectsInsideCatchThatAwaitsSecondPromise(): void
222223
{
223224
$promise = async(function () {
224225
try {
@@ -238,7 +239,7 @@ public function testCancelAsycWillReturnPendigPromiseWhenCancellingFirstPromiseR
238239
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
239240
}
240241

241-
public function testCancelAsyncWillCancelNestedAwait()
242+
public function testCancelAsyncWillCancelNestedAwait(): void
242243
{
243244
self::expectOutputString('abc');
244245
$this->expectException(\RuntimeException::class);

0 commit comments

Comments
 (0)