Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Add template annotations #247

Merged
merged 1 commit into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ jobs:
- 7.4
- 7.3
- 7.2
- 7.1
clue marked this conversation as resolved.
Show resolved Hide resolved
steps:
- uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2
Expand Down
12 changes: 10 additions & 2 deletions src/Deferred.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

namespace React\Promise;

/**
* @template T
*/
final class Deferred
{
/** @var Promise */
/**
* @var PromiseInterface<T>
*/
private $promise;

/** @var callable */
Expand All @@ -21,13 +26,16 @@ public function __construct(callable $canceller = null)
}, $canceller);
}

/**
* @return PromiseInterface<T>
clue marked this conversation as resolved.
Show resolved Hide resolved
*/
public function promise(): PromiseInterface
{
return $this->promise;
}

/**
* @param mixed $value
* @param T $value
*/
public function resolve($value): void
{
Expand Down
18 changes: 15 additions & 3 deletions src/Internal/FulfilledPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@

/**
* @internal
*
* @template T
* @template-implements PromiseInterface<T>
*/
final class FulfilledPromise implements PromiseInterface
{
/** @var mixed */
/** @var T */
private $value;

/**
* @param mixed $value
* @param T $value
* @throws \InvalidArgumentException
*/
public function __construct($value = null)
Expand All @@ -26,14 +29,23 @@ public function __construct($value = null)
$this->value = $value;
}

/**
* @template TFulfilled
* @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
* @return PromiseInterface<($onFulfilled is null ? T : TFulfilled)>
*/
public function then(callable $onFulfilled = null, callable $onRejected = null): PromiseInterface
{
if (null === $onFulfilled) {
return $this;
}

try {
return resolve($onFulfilled($this->value));
/**
* @var PromiseInterface<T>|T $result
*/
$result = $onFulfilled($this->value);
return resolve($result);
} catch (\Throwable $exception) {
return new RejectedPromise($exception);
}
Expand Down
17 changes: 17 additions & 0 deletions src/Internal/RejectedPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

/**
* @internal
*
* @template-implements PromiseInterface<never>
*/
final class RejectedPromise implements PromiseInterface
{
Expand Down Expand Up @@ -37,6 +39,12 @@ public function __destruct()
\error_log($message);
}

/**
* @template TRejected
* @param ?callable $onFulfilled
* @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
* @return PromiseInterface<($onRejected is null ? never : TRejected)>
*/
public function then(callable $onFulfilled = null, callable $onRejected = null): PromiseInterface
{
if (null === $onRejected) {
Expand All @@ -52,12 +60,21 @@ public function then(callable $onFulfilled = null, callable $onRejected = null):
}
}

/**
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<TRejected>
*/
public function catch(callable $onRejected): PromiseInterface
{
if (!_checkTypehint($onRejected, $this->reason)) {
return $this;
}

/**
* @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
*/
return $this->then(null, $onRejected);
}

Expand Down
23 changes: 22 additions & 1 deletion src/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

use React\Promise\Internal\RejectedPromise;

/**
* @template T
* @template-implements PromiseInterface<T>
*/
final class Promise implements PromiseInterface
{
/** @var ?callable */
private $canceller;

/** @var ?PromiseInterface */
/** @var ?PromiseInterface<T> */
private $result;

/** @var callable[] */
Expand Down Expand Up @@ -66,13 +70,22 @@ static function () use (&$parent) {
);
}

/**
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<T|TRejected>
*/
public function catch(callable $onRejected): PromiseInterface
{
return $this->then(null, static function ($reason) use ($onRejected) {
if (!_checkTypehint($onRejected, $reason)) {
return new RejectedPromise($reason);
}

/**
* @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
*/
return $onRejected($reason);
});
}
Expand Down Expand Up @@ -175,6 +188,9 @@ private function reject(\Throwable $reason): void
$this->settle(reject($reason));
}

/**
* @param PromiseInterface<T> $result
*/
private function settle(PromiseInterface $result): void
{
$result = $this->unwrap($result);
Expand Down Expand Up @@ -207,9 +223,14 @@ private function settle(PromiseInterface $result): void
}
}

/**
* @param PromiseInterface<T> $promise
* @return PromiseInterface<T>
*/
private function unwrap(PromiseInterface $promise): PromiseInterface
{
while ($promise instanceof self && null !== $promise->result) {
/** @var PromiseInterface<T> $promise */
$promise = $promise->result;
}

Expand Down
31 changes: 20 additions & 11 deletions src/PromiseInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace React\Promise;

/**
* @template-covariant T
*/
interface PromiseInterface
{
/**
Expand All @@ -28,9 +31,11 @@ interface PromiseInterface
* 2. `$onFulfilled` and `$onRejected` will never be called more
* than once.
*
* @param callable|null $onFulfilled
* @param callable|null $onRejected
* @return PromiseInterface
* @template TFulfilled
* @template TRejected
* @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
* @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
* @return PromiseInterface<($onRejected is null ? ($onFulfilled is null ? T : TFulfilled) : ($onFulfilled is null ? T|TRejected : TFulfilled|TRejected))>
*/
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface;

Expand All @@ -44,8 +49,10 @@ public function then(?callable $onFulfilled = null, ?callable $onRejected = null
* Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
* only specific errors.
*
* @param callable $onRejected
* @return PromiseInterface
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<T|TRejected>
*/
public function catch(callable $onRejected): PromiseInterface;

Expand Down Expand Up @@ -91,8 +98,8 @@ public function catch(callable $onRejected): PromiseInterface;
* ->finally('cleanup');
* ```
*
* @param callable $onFulfilledOrRejected
* @return PromiseInterface
* @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
* @return PromiseInterface<T>
*/
public function finally(callable $onFulfilledOrRejected): PromiseInterface;

Expand All @@ -117,8 +124,10 @@ public function cancel(): void;
* $promise->catch($onRejected);
* ```
*
* @param callable $onRejected
* @return PromiseInterface
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<T|TRejected>
* @deprecated 3.0.0 Use catch() instead
* @see self::catch()
*/
Expand All @@ -134,8 +143,8 @@ public function otherwise(callable $onRejected): PromiseInterface;
* $promise->finally($onFulfilledOrRejected);
* ```
*
* @param callable $onFulfilledOrRejected
* @return PromiseInterface
* @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
* @return PromiseInterface<T>
WyriHaximus marked this conversation as resolved.
Show resolved Hide resolved
* @deprecated 3.0.0 Use finally() instead
* @see self::finally()
*/
Expand Down
26 changes: 15 additions & 11 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
*
* If `$promiseOrValue` is a promise, it will be returned as is.
*
* @param mixed $promiseOrValue
* @return PromiseInterface
* @template T
* @param PromiseInterface<T>|T $promiseOrValue
* @return PromiseInterface<T>
*/
function resolve($promiseOrValue): PromiseInterface
{
Expand All @@ -31,6 +32,7 @@ function resolve($promiseOrValue): PromiseInterface

if (\method_exists($promiseOrValue, 'cancel')) {
$canceller = [$promiseOrValue, 'cancel'];
assert(\is_callable($canceller));
}

return new Promise(function ($resolve, $reject) use ($promiseOrValue): void {
Expand All @@ -54,8 +56,7 @@ function resolve($promiseOrValue): PromiseInterface
* throwing an exception. For example, it allows you to propagate a rejection with
* the value of another promise.
*
* @param \Throwable $reason
* @return PromiseInterface
* @return PromiseInterface<never>
*/
function reject(\Throwable $reason): PromiseInterface
{
Expand All @@ -68,8 +69,9 @@ function reject(\Throwable $reason): PromiseInterface
* will be an array containing the resolution values of each of the items in
* `$promisesOrValues`.
*
* @param iterable<mixed> $promisesOrValues
* @return PromiseInterface
* @template T
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
* @return PromiseInterface<array<T>>
*/
function all(iterable $promisesOrValues): PromiseInterface
{
Expand Down Expand Up @@ -119,14 +121,15 @@ function (\Throwable $reason) use (&$continue, $reject): void {
* The returned promise will become **infinitely pending** if `$promisesOrValues`
* contains 0 items.
*
* @param iterable<mixed> $promisesOrValues
* @return PromiseInterface
* @template T
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
* @return PromiseInterface<T>
*/
function race(iterable $promisesOrValues): PromiseInterface
{
$cancellationQueue = new Internal\CancellationQueue();

return new Promise(function ($resolve, $reject) use ($promisesOrValues, $cancellationQueue): void {
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
$continue = true;

foreach ($promisesOrValues as $promiseOrValue) {
Expand Down Expand Up @@ -154,8 +157,9 @@ function race(iterable $promisesOrValues): PromiseInterface
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
* if `$promisesOrValues` contains 0 items.
*
* @param iterable<mixed> $promisesOrValues
* @return PromiseInterface
* @template T
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
* @return PromiseInterface<T>
*/
function any(iterable $promisesOrValues): PromiseInterface
{
Expand Down
8 changes: 7 additions & 1 deletion tests/DeferredTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@

use React\Promise\PromiseAdapter\CallbackPromiseAdapter;

/**
* @template T
*/
class DeferredTest extends TestCase
{
use PromiseTest\FullTestTrait;

/**
* @return CallbackPromiseAdapter<T>
*/
public function getPromiseTestAdapter(callable $canceller = null): CallbackPromiseAdapter
{
$d = new Deferred($canceller);
Expand Down Expand Up @@ -54,7 +60,7 @@ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenc
gc_collect_cycles();
gc_collect_cycles(); // clear twice to avoid leftovers in PHP 7.4 with ext-xdebug and code coverage turned on

/** @var Deferred $deferred */
/** @var Deferred<never> $deferred */
$deferred = new Deferred(function () use (&$deferred) {
assert($deferred instanceof Deferred);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use function React\Promise\reject;

require __DIR__ . '/../vendor/autoload.php';

reject(new RuntimeException('foo'))->then(null, function (UnexpectedValueException $unexpected): void {
reject(new RuntimeException('foo'))->then(null, function (UnexpectedValueException $unexpected): void { // @phpstan-ignore-line
echo 'This will never be shown because the types do not match' . PHP_EOL;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use function React\Promise\reject;

require __DIR__ . '/../vendor/autoload.php';

reject(new RuntimeException('foo'))->then(null, function (UnexpectedValueException $unexpected): void {
reject(new RuntimeException('foo'))->then(null, function (UnexpectedValueException $unexpected): void { // @phpstan-ignore-line
echo 'This will never be shown because the types do not match' . PHP_EOL;
});

Expand Down
3 changes: 3 additions & 0 deletions tests/Internal/CancellationQueueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ public function rethrowsExceptionsThrownFromCancel(): void
$cancellationQueue();
}

/**
* @return Deferred<never>
*/
private function getCancellableDeferred(): Deferred
{
return new Deferred($this->expectCallableOnce());
Expand Down
Loading