diff --git a/README.md b/README.md index ea5541e..38ab622 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,7 @@ $promise = new React\Promise\Promise($resolver, $canceller); ``` The promise constructor receives a resolver function and an optional canceller -function which both will be called with 3 arguments: +function which both will be called with two arguments: * `$resolve($value)` - Primary function that seals the fate of the returned promise. Accepts either a non-promise value, or another promise. diff --git a/src/Deferred.php b/src/Deferred.php index 53d945e..dac6b2b 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -12,12 +12,15 @@ final class Deferred */ private $promise; - /** @var callable */ + /** @var callable(T):void */ private $resolveCallback; - /** @var callable */ + /** @var callable(\Throwable):void */ private $rejectCallback; + /** + * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller + */ public function __construct(callable $canceller = null) { $this->promise = new Promise(function ($resolve, $reject): void { diff --git a/src/Promise.php b/src/Promise.php index 1613db5..c46b41f 100644 --- a/src/Promise.php +++ b/src/Promise.php @@ -10,13 +10,13 @@ */ final class Promise implements PromiseInterface { - /** @var ?callable */ + /** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */ private $canceller; /** @var ?PromiseInterface */ private $result; - /** @var callable[] */ + /** @var list):void> */ private $handlers = []; /** @var int */ @@ -25,6 +25,10 @@ final class Promise implements PromiseInterface /** @var bool */ private $cancelled = false; + /** + * @param callable(callable(T):void,callable(\Throwable):void):void $resolver + * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller + */ public function __construct(callable $resolver, callable $canceller = null) { $this->canceller = $canceller; @@ -57,7 +61,7 @@ public function then(callable $onFulfilled = null, callable $onRejected = null): return new static( $this->resolver($onFulfilled, $onRejected), - static function () use (&$parent) { + static function () use (&$parent): void { assert($parent instanceof self); --$parent->requiredCancelRequests; @@ -78,7 +82,7 @@ static function () use (&$parent) { */ public function catch(callable $onRejected): PromiseInterface { - return $this->then(null, static function ($reason) use ($onRejected) { + return $this->then(null, static function (\Throwable $reason) use ($onRejected) { if (!_checkTypehint($onRejected, $reason)) { return new RejectedPromise($reason); } @@ -92,12 +96,12 @@ public function catch(callable $onRejected): PromiseInterface public function finally(callable $onFulfilledOrRejected): PromiseInterface { - return $this->then(static function ($value) use ($onFulfilledOrRejected) { + return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface { return resolve($onFulfilledOrRejected())->then(function () use ($value) { return $value; }); - }, static function ($reason) use ($onFulfilledOrRejected) { - return resolve($onFulfilledOrRejected())->then(function () use ($reason) { + }, static function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface { + return resolve($onFulfilledOrRejected())->then(function () use ($reason): RejectedPromise { return new RejectedPromise($reason); }); }); @@ -164,12 +168,12 @@ public function always(callable $onFulfilledOrRejected): PromiseInterface private function resolver(callable $onFulfilled = null, callable $onRejected = null): callable { - return function ($resolve, $reject) use ($onFulfilled, $onRejected) { - $this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject) { + return function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected): void { + $this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject): void { $promise = $promise->then($onFulfilled, $onRejected); if ($promise instanceof self && $promise->result === null) { - $promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject) { + $promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject): void { $promise->then($resolve, $reject); }; } else { @@ -237,6 +241,9 @@ private function unwrap(PromiseInterface $promise): PromiseInterface return $promise; } + /** + * @param callable(callable(mixed):void,callable(\Throwable):void):void $cb + */ private function call(callable $cb): void { // Explicitly overwrite argument with null value. This ensure that this @@ -274,13 +281,13 @@ private function call(callable $cb): void $target =& $this; $callback( - static function ($value) use (&$target) { + static function ($value) use (&$target): void { if ($target !== null) { $target->settle(resolve($value)); $target = null; } }, - static function (\Throwable $reason) use (&$target) { + static function (\Throwable $reason) use (&$target): void { if ($target !== null) { $target->reject($reason); $target = null; diff --git a/src/functions.php b/src/functions.php index ac3a66c..2aab877 100644 --- a/src/functions.php +++ b/src/functions.php @@ -35,7 +35,8 @@ function resolve($promiseOrValue): PromiseInterface assert(\is_callable($canceller)); } - return new Promise(function ($resolve, $reject) use ($promiseOrValue): void { + /** @var Promise */ + return new Promise(function (callable $resolve, callable $reject) use ($promiseOrValue): void { $promiseOrValue->then($resolve, $reject); }, $canceller); } @@ -77,7 +78,8 @@ function all(iterable $promisesOrValues): PromiseInterface { $cancellationQueue = new Internal\CancellationQueue(); - return new Promise(function ($resolve, $reject) use ($promisesOrValues, $cancellationQueue): void { + /** @var Promise> */ + return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { $toResolve = 0; /** @var bool */ $continue = true; @@ -129,6 +131,7 @@ function race(iterable $promisesOrValues): PromiseInterface { $cancellationQueue = new Internal\CancellationQueue(); + /** @var Promise */ return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { $continue = true; @@ -165,7 +168,8 @@ function any(iterable $promisesOrValues): PromiseInterface { $cancellationQueue = new Internal\CancellationQueue(); - return new Promise(function ($resolve, $reject) use ($promisesOrValues, $cancellationQueue): void { + /** @var Promise */ + return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { $toReject = 0; $continue = true; $reasons = []; diff --git a/tests/Internal/CancellationQueueTest.php b/tests/Internal/CancellationQueueTest.php index fea5696..e8fd58e 100644 --- a/tests/Internal/CancellationQueueTest.php +++ b/tests/Internal/CancellationQueueTest.php @@ -101,6 +101,7 @@ public function rethrowsExceptionsThrownFromCancel(): void */ private function getCancellableDeferred(): Deferred { + /** @var Deferred */ return new Deferred($this->expectCallableOnce()); } } diff --git a/tests/PromiseTest.php b/tests/PromiseTest.php index 74a1446..34411dc 100644 --- a/tests/PromiseTest.php +++ b/tests/PromiseTest.php @@ -19,11 +19,14 @@ public function getPromiseTestAdapter(callable $canceller = null): CallbackPromi { $resolveCallback = $rejectCallback = null; - $promise = new Promise(function ($resolve, $reject) use (&$resolveCallback, &$rejectCallback) { + $promise = new Promise(function (callable $resolve, callable $reject) use (&$resolveCallback, &$rejectCallback): void { $resolveCallback = $resolve; $rejectCallback = $reject; }, $canceller); + assert(is_callable($resolveCallback)); + assert(is_callable($rejectCallback)); + return new CallbackPromiseAdapter([ 'promise' => function () use ($promise) { return $promise; diff --git a/tests/types/deferred.php b/tests/types/deferred.php index ffdea72..d468849 100644 --- a/tests/types/deferred.php +++ b/tests/types/deferred.php @@ -10,3 +10,40 @@ $deferredB = new Deferred(); $deferredB->resolve(42); assertType('React\Promise\PromiseInterface', $deferredB->promise()); + +// $deferred = new Deferred(); +// $deferred->resolve(42); +// assertType('React\Promise\Deferred', $deferred); + +// $deferred = new Deferred(); +// $deferred->resolve(true); +// $deferred->resolve('ignored'); +// assertType('React\Promise\Deferred', $deferred); + +// $deferred = new Deferred(); +// $deferred->reject(new \RuntimeException()); +// assertType('React\Promise\Deferred', $deferred); + +// invalid number of arguments passed to $canceller +/** @phpstan-ignore-next-line */ +$deferred = new Deferred(function ($a, $b, $c) { }); +assertType('React\Promise\Deferred', $deferred); + +// invalid types for arguments of $canceller +/** @phpstan-ignore-next-line */ +$deferred = new Deferred(function (int $a, string $b) { }); +assertType('React\Promise\Deferred', $deferred); + +// invalid number of arguments passed to $resolve +$deferred = new Deferred(function (callable $resolve) { + /** @phpstan-ignore-next-line */ + $resolve(); +}); +assertType('React\Promise\Deferred', $deferred); + +// invalid type passed to $reject +$deferred = new Deferred(function (callable $resolve, callable $reject) { + /** @phpstan-ignore-next-line */ + $reject(2); +}); +assertType('React\Promise\Deferred', $deferred); diff --git a/tests/types/promise.php b/tests/types/promise.php new file mode 100644 index 0000000..fa5b15c --- /dev/null +++ b/tests/types/promise.php @@ -0,0 +1,91 @@ +', $promise); + +// $promise = new Promise(function (callable $resolve): void { +// $resolve(42); +// }); +// assertType('React\Promise\PromiseInterface', $promise); + +// $promise = new Promise(function (callable $resolve): void { +// $resolve(true); +// $resolve('ignored'); +// }); +// assertType('React\Promise\PromiseInterface', $promise); + +// $promise = new Promise(function (callable $resolve, callable $reject): void { +// $reject(new \RuntimeException()); +// }); +// assertType('React\Promise\PromiseInterface', $promise); + +// $promise = new Promise(function (): never { +// throw new \RuntimeException(); +// }); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid number of arguments for $resolver +/** @phpstan-ignore-next-line */ +$promise = new Promise(function ($a, $b, $c) { }); +assert($promise instanceof Promise); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid types for arguments of $resolver +/** @phpstan-ignore-next-line */ +$promise = new Promise(function (int $a, string $b) { }); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid number of arguments passed to $resolve +$promise = new Promise(function (callable $resolve) { + /** @phpstan-ignore-next-line */ + $resolve(); +}); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid number of arguments passed to $reject +$promise = new Promise(function (callable $resolve, callable $reject) { + /** @phpstan-ignore-next-line */ + $reject(); +}); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid type passed to $reject +$promise = new Promise(function (callable $resolve, callable $reject) { + /** @phpstan-ignore-next-line */ + $reject(2); +}); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid number of arguments for $canceller +/** @phpstan-ignore-next-line */ +$promise = new Promise(function () { }, function ($a, $b, $c) { }); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid types for arguments of $canceller +/** @phpstan-ignore-next-line */ +$promise = new Promise(function () { }, function (int $a, string $b) { }); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid number of arguments passed to $resolve +$promise = new Promise(function () { }, function (callable $resolve) { + /** @phpstan-ignore-next-line */ + $resolve(); +}); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid number of arguments passed to $reject +$promise = new Promise(function () { }, function (callable $resolve, callable $reject) { + /** @phpstan-ignore-next-line */ + $reject(); +}); +// assertType('React\Promise\PromiseInterface', $promise); + +// invalid type passed to $reject +$promise = new Promise(function() { }, function (callable $resolve, callable $reject) { + /** @phpstan-ignore-next-line */ + $reject(2); +}); +// assertType('React\Promise\PromiseInterface', $promise);