Skip to content
Snippets Groups Projects
Commit 6d62e4ba authored by David Prévot's avatar David Prévot
Browse files

New upstream version 3.1.0

parents 7c5dfe04 e563d55d
No related branches found
No related tags found
No related merge requests found
......@@ -11,6 +11,7 @@ jobs:
strategy:
matrix:
php:
- 8.3
- 8.2
- 8.1
- 8.0
......@@ -19,7 +20,7 @@ jobs:
- 7.2
- 7.1
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
......@@ -37,6 +38,7 @@ jobs:
strategy:
matrix:
php:
- 8.3
- 8.2
- 8.1
- 8.0
......@@ -44,7 +46,7 @@ jobs:
- 7.3
- 7.2
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
......
# Changelog
## 3.1.0 (2023-11-16)
* Feature: Full PHP 8.3 compatibility.
(#255 by @clue)
* Feature: Describe all callable arguments with types for `Promise` and `Deferred`.
(#253 by @clue)
* Update test suite and minor documentation improvements.
(#251 by @ondrejmirtes and #250 by @SQKo)
## 3.0.0 (2023-07-11)
A major new feature release, see [**release announcement**](https://clue.engineering/2023/announcing-reactphp-promise-v3).
......
......@@ -55,7 +55,7 @@ It also provides several other useful promise-related concepts, such as joining
multiple promises and mapping and reducing collections of promises.
If you've never heard about promises before,
[read this first](https://gist.github.com/3889970).
[read this first](https://gist.github.com/domenic/3889970).
Concepts
--------
......@@ -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.
......@@ -664,7 +664,7 @@ This project follows [SemVer](https://semver.org/).
This will install the latest supported version from this branch:
```bash
composer require react/promise:^3
composer require react/promise:^3.1
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
......
......@@ -28,8 +28,8 @@
"php": ">=7.1.0"
},
"require-dev": {
"phpstan/phpstan": "1.10.20 || 1.4.10",
"phpunit/phpunit": "^9.5 || ^7.5"
"phpstan/phpstan": "1.10.39 || 1.4.10",
"phpunit/phpunit": "^9.6 || ^7.5"
},
"autoload": {
"psr-4": {
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- PHPUnit configuration file with new format for PHPUnit 9.5+ -->
<!-- PHPUnit configuration file with new format for PHPUnit 9.6+ -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheResult="false"
colors="true"
......@@ -24,7 +24,7 @@
<php>
<ini name="error_reporting" value="-1" />
<!-- Evaluate assertions, requires running with "php -d zend.assertions=1 vendor/bin/phpunit" -->
<!-- <ini name="zend.assertions=1" value="1" /> -->
<!-- <ini name="zend.assertions" value="1" /> -->
<ini name="assert.active" value="1" />
<ini name="assert.exception" value="1" />
<ini name="assert.bail" value="0" />
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- PHPUnit configuration file with old format before PHPUnit 9 -->
<!-- PHPUnit configuration file with old format for legacy PHPUnit -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
......@@ -22,7 +22,7 @@
<php>
<ini name="error_reporting" value="-1" />
<!-- Evaluate assertions, requires running with "php -d zend.assertions=1 vendor/bin/phpunit" -->
<!-- <ini name="zend.assertions=1" value="1" /> -->
<!-- <ini name="zend.assertions" value="1" /> -->
<ini name="assert.active" value="1" />
<ini name="assert.exception" value="1" />
<ini name="assert.bail" value="0" />
......
......@@ -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 {
......
......@@ -10,13 +10,13 @@ use React\Promise\Internal\RejectedPromise;
*/
final class Promise implements PromiseInterface
{
/** @var ?callable */
/** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */
private $canceller;
/** @var ?PromiseInterface<T> */
private $result;
/** @var callable[] */
/** @var list<callable(PromiseInterface<T>):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 @@ final class Promise implements PromiseInterface
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 @@ final class Promise implements PromiseInterface
*/
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 @@ final class Promise implements 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 @@ final class Promise implements 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 @@ final class Promise implements 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 @@ final class Promise implements PromiseInterface
$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;
......
......@@ -35,7 +35,8 @@ function resolve($promiseOrValue): PromiseInterface
assert(\is_callable($canceller));
}
return new Promise(function ($resolve, $reject) use ($promiseOrValue): void {
/** @var Promise<T> */
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<array<T>> */
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<T> */
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<T> */
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
$toReject = 0;
$continue = true;
$reasons = [];
......
......@@ -101,6 +101,7 @@ class CancellationQueueTest extends TestCase
*/
private function getCancellableDeferred(): Deferred
{
/** @var Deferred<never> */
return new Deferred($this->expectCallableOnce());
}
}
......@@ -19,11 +19,14 @@ class PromiseTest extends TestCase
{
$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;
......
......@@ -10,3 +10,40 @@ assertType('React\Promise\PromiseInterface<mixed>', $deferredA->promise());
$deferredB = new Deferred();
$deferredB->resolve(42);
assertType('React\Promise\PromiseInterface<int>', $deferredB->promise());
// $deferred = new Deferred();
// $deferred->resolve(42);
// assertType('React\Promise\Deferred<int>', $deferred);
// $deferred = new Deferred();
// $deferred->resolve(true);
// $deferred->resolve('ignored');
// assertType('React\Promise\Deferred<bool>', $deferred);
// $deferred = new Deferred();
// $deferred->reject(new \RuntimeException());
// assertType('React\Promise\Deferred<never>', $deferred);
// invalid number of arguments passed to $canceller
/** @phpstan-ignore-next-line */
$deferred = new Deferred(function ($a, $b, $c) { });
assertType('React\Promise\Deferred<mixed>', $deferred);
// invalid types for arguments of $canceller
/** @phpstan-ignore-next-line */
$deferred = new Deferred(function (int $a, string $b) { });
assertType('React\Promise\Deferred<mixed>', $deferred);
// invalid number of arguments passed to $resolve
$deferred = new Deferred(function (callable $resolve) {
/** @phpstan-ignore-next-line */
$resolve();
});
assertType('React\Promise\Deferred<mixed>', $deferred);
// invalid type passed to $reject
$deferred = new Deferred(function (callable $resolve, callable $reject) {
/** @phpstan-ignore-next-line */
$reject(2);
});
assertType('React\Promise\Deferred<mixed>', $deferred);
<?php
use React\Promise\Promise;
use function PHPStan\Testing\assertType;
// $promise = new Promise(function (): void { });
// assertType('React\Promise\PromiseInterface<never>', $promise);
// $promise = new Promise(function (callable $resolve): void {
// $resolve(42);
// });
// assertType('React\Promise\PromiseInterface<int>', $promise);
// $promise = new Promise(function (callable $resolve): void {
// $resolve(true);
// $resolve('ignored');
// });
// assertType('React\Promise\PromiseInterface<bool>', $promise);
// $promise = new Promise(function (callable $resolve, callable $reject): void {
// $reject(new \RuntimeException());
// });
// assertType('React\Promise\PromiseInterface<never>', $promise);
// $promise = new Promise(function (): never {
// throw new \RuntimeException();
// });
// assertType('React\Promise\PromiseInterface<never>', $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<never>', $promise);
// invalid types for arguments of $resolver
/** @phpstan-ignore-next-line */
$promise = new Promise(function (int $a, string $b) { });
// assertType('React\Promise\PromiseInterface<never>', $promise);
// invalid number of arguments passed to $resolve
$promise = new Promise(function (callable $resolve) {
/** @phpstan-ignore-next-line */
$resolve();
});
// assertType('React\Promise\PromiseInterface<never>', $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<never>', $promise);
// invalid type passed to $reject
$promise = new Promise(function (callable $resolve, callable $reject) {
/** @phpstan-ignore-next-line */
$reject(2);
});
// assertType('React\Promise\PromiseInterface<never>', $promise);
// invalid number of arguments for $canceller
/** @phpstan-ignore-next-line */
$promise = new Promise(function () { }, function ($a, $b, $c) { });
// assertType('React\Promise\PromiseInterface<never>', $promise);
// invalid types for arguments of $canceller
/** @phpstan-ignore-next-line */
$promise = new Promise(function () { }, function (int $a, string $b) { });
// assertType('React\Promise\PromiseInterface<never>', $promise);
// invalid number of arguments passed to $resolve
$promise = new Promise(function () { }, function (callable $resolve) {
/** @phpstan-ignore-next-line */
$resolve();
});
// assertType('React\Promise\PromiseInterface<never>', $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<never>', $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<never>', $promise);
......@@ -5,9 +5,9 @@ use function PHPStan\Testing\assertType;
use function React\Promise\reject;
use function React\Promise\resolve;
assertType('React\Promise\PromiseInterface<*NEVER*>', reject(new RuntimeException()));
assertType('React\Promise\PromiseInterface<*NEVER*>', reject(new RuntimeException())->then(null, null));
// assertType('React\Promise\PromiseInterface<*NEVER*>', reject(new RuntimeException())->then(function (): int {
assertType('React\Promise\PromiseInterface<never>', reject(new RuntimeException()));
assertType('React\Promise\PromiseInterface<never>', reject(new RuntimeException())->then(null, null));
// assertType('React\Promise\PromiseInterface<never>', reject(new RuntimeException())->then(function (): int {
// return 42;
// }));
assertType('React\Promise\PromiseInterface<int>', reject(new RuntimeException())->then(null, function (): int {
......@@ -32,11 +32,11 @@ assertType('React\Promise\PromiseInterface<int>', reject(new RuntimeException())
return resolve(42);
}));
assertType('React\Promise\PromiseInterface<*NEVER*>', reject(new RuntimeException())->finally(function (): void { }));
assertType('React\Promise\PromiseInterface<*NEVER*>', reject(new RuntimeException())->finally(function (): never {
assertType('React\Promise\PromiseInterface<never>', reject(new RuntimeException())->finally(function (): void { }));
assertType('React\Promise\PromiseInterface<never>', reject(new RuntimeException())->finally(function (): never {
throw new \UnexpectedValueException();
}));
assertType('React\Promise\PromiseInterface<*NEVER*>', reject(new RuntimeException())->finally(function (): PromiseInterface {
assertType('React\Promise\PromiseInterface<never>', reject(new RuntimeException())->finally(function (): PromiseInterface {
return reject(new \UnexpectedValueException());
}));
......@@ -50,10 +50,10 @@ assertType('React\Promise\PromiseInterface<int>', reject(new RuntimeException())
return resolve(42);
}));
assertType('React\Promise\PromiseInterface<*NEVER*>', reject(new RuntimeException())->always(function (): void { }));
assertType('React\Promise\PromiseInterface<*NEVER*>', reject(new RuntimeException())->always(function (): never {
assertType('React\Promise\PromiseInterface<never>', reject(new RuntimeException())->always(function (): void { }));
assertType('React\Promise\PromiseInterface<never>', reject(new RuntimeException())->always(function (): never {
throw new \UnexpectedValueException();
}));
assertType('React\Promise\PromiseInterface<*NEVER*>', reject(new RuntimeException())->always(function (): PromiseInterface {
assertType('React\Promise\PromiseInterface<never>', reject(new RuntimeException())->always(function (): PromiseInterface {
return reject(new \UnexpectedValueException());
}));
......@@ -34,7 +34,7 @@ assertType('React\Promise\PromiseInterface<int>', resolve(true)->then(function (
assertType('React\Promise\PromiseInterface<int>', resolve(true)->then(function (bool $value): PromiseInterface {
return resolve(42);
}));
assertType('React\Promise\PromiseInterface<*NEVER*>', resolve(true)->then(function (bool $value): never {
assertType('React\Promise\PromiseInterface<never>', resolve(true)->then(function (bool $value): never {
throw new \RuntimeException();
}));
assertType('React\Promise\PromiseInterface<bool|int>', resolve(true)->then(null, function (\Throwable $e): int {
......@@ -61,10 +61,10 @@ assertType('React\Promise\PromiseInterface<bool|int>', resolve(true)->catch(func
}));
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->finally(function (): void { }));
// assertType('React\Promise\PromiseInterface<*NEVER*>', resolve(true)->finally(function (): never {
// assertType('React\Promise\PromiseInterface<never>', resolve(true)->finally(function (): never {
// throw new \RuntimeException();
// }));
// assertType('React\Promise\PromiseInterface<*NEVER*>', resolve(true)->finally(function (): PromiseInterface {
// assertType('React\Promise\PromiseInterface<never>', resolve(true)->finally(function (): PromiseInterface {
// return reject(new \RuntimeException());
// }));
......@@ -79,9 +79,9 @@ assertType('React\Promise\PromiseInterface<bool|int>', resolve(true)->otherwise(
}));
assertType('React\Promise\PromiseInterface<bool>', resolve(true)->always(function (): void { }));
// assertType('React\Promise\PromiseInterface<*NEVER*>', resolve(true)->always(function (): never {
// assertType('React\Promise\PromiseInterface<never>', resolve(true)->always(function (): never {
// throw new \RuntimeException();
// }));
// assertType('React\Promise\PromiseInterface<*NEVER*>', resolve(true)->always(function (): PromiseInterface {
// assertType('React\Promise\PromiseInterface<never>', resolve(true)->always(function (): PromiseInterface {
// return reject(new \RuntimeException());
// }));
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment