From b5b5fa7bf450f07454645ec0b99b57b238151c03 Mon Sep 17 00:00:00 2001 From: Niklas Keller Date: Sat, 4 Apr 2020 16:48:52 +0200 Subject: [PATCH] Add psalm annotations and fix doc types --- .travis.yml | 2 ++ composer.json | 11 ++++--- examples/benchmark-throughput.php | 13 +++++--- lib/InMemoryStream.php | 2 +- lib/InputStream.php | 2 ++ lib/InputStreamChain.php | 2 ++ lib/IteratorStream.php | 7 ++++ lib/LineReader.php | 3 ++ lib/Message.php | 8 ++--- lib/OutputBuffer.php | 5 +-- lib/ResourceInputStream.php | 18 +++++++++-- lib/ResourceOutputStream.php | 21 ++++++++---- lib/ZlibInputStream.php | 12 ++++++- lib/ZlibOutputStream.php | 13 +++++++- psalm.xml | 53 +++++++++++++++++++++++++++++++ 15 files changed, 144 insertions(+), 28 deletions(-) create mode 100644 psalm.xml diff --git a/.travis.yml b/.travis.yml index 4f8956f..5bc6d1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ env: - AMP_DEBUG=true install: + - if [[ ${TRAVIS_PHP_VERSION:0:3} == "7.0" ]]; then composer remove --dev vimeo/psalm; fi - composer update -n --prefer-dist - wget https://github.com/php-coveralls/php-coveralls/releases/download/v1.0.2/coveralls.phar - chmod +x coveralls.phar @@ -26,6 +27,7 @@ install: script: - vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml - PHP_CS_FIXER_IGNORE_ENV=1 php vendor/bin/php-cs-fixer --diff --dry-run -v fix + - if [[ ${TRAVIS_PHP_VERSION:0:3} == "7.0" ]]; then echo "Skipped psalm static analysis"; else vendor/bin/psalm; fi after_script: - ./coveralls.phar -v diff --git a/composer.json b/composer.json index 71d7330..fb9b1f3 100644 --- a/composer.json +++ b/composer.json @@ -30,10 +30,11 @@ }, "require-dev": { "amphp/phpunit-util": "^1", - "phpunit/phpunit": "^6", + "phpunit/phpunit": "^6 || ^7 || ^8", "friendsofphp/php-cs-fixer": "^2.3", "amphp/php-cs-fixer-config": "dev-master", - "infection/infection": "^0.9.3" + "vimeo/psalm": "^3.9@dev", + "jetbrains/phpstorm-stubs": "^2019.3" }, "autoload": { "psr-4": { @@ -48,9 +49,9 @@ "Amp\\ByteStream\\Test\\": "test" } }, - "config": { - "platform": { - "php": "7.0.0" + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" } } } diff --git a/examples/benchmark-throughput.php b/examples/benchmark-throughput.php index 7114f81..d3486d3 100644 --- a/examples/benchmark-throughput.php +++ b/examples/benchmark-throughput.php @@ -12,9 +12,11 @@ Loop::set(new Loop\NativeDriver()); $args = \getopt('i:o:t:'); -$if = isset($args['i']) ? $args['i'] : '/dev/zero'; -$of = isset($args['o']) ? $args['o'] : '/dev/null'; -$t = isset($args['t']) ? $args['t'] : 30; +$if = $args['i'] ?? '/dev/zero'; +$of = $args['o'] ?? '/dev/null'; +$t = (int) ($args['t'] ?? 30); + +\assert(\is_string($if) && \is_string($of)); // passing file descriptors requires mapping paths (https://bugs.php.net/bug.php?id=53465) $if = \preg_replace('(^/dev/fd/)', 'php://fd/', $if); @@ -49,7 +51,10 @@ $t = \microtime(true) - $start; - $bytes = \ftell($out->getResource()); + $resource = $out->getResource(); + \assert($resource !== null); + + $bytes = \ftell($resource); $stderr->write('read ' . $bytes . ' byte(s) in ' . \round($t, 3) . ' second(s) => ' . \round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL); $stderr->write('peak memory usage of ' . \round(\memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL); diff --git a/lib/InMemoryStream.php b/lib/InMemoryStream.php index f171625..c764f41 100644 --- a/lib/InMemoryStream.php +++ b/lib/InMemoryStream.php @@ -23,7 +23,7 @@ public function __construct(string $contents = null) /** * Reads data from the stream. * - * @return Promise Resolves with the full contents or `null` if the stream has closed / already been consumed. + * @return Promise Resolves with the full contents or `null` if the stream has closed / already been consumed. */ public function read(): Promise { diff --git a/lib/InputStream.php b/lib/InputStream.php index 10b7d21..4c0b9e8 100644 --- a/lib/InputStream.php +++ b/lib/InputStream.php @@ -30,6 +30,8 @@ interface InputStream * * @return Promise Resolves with a string when new data is available or `null` if the stream has closed. * + * @psalm-return Promise + * * @throws PendingReadError Thrown if another read operation is still pending. */ public function read(): Promise; diff --git a/lib/InputStreamChain.php b/lib/InputStreamChain.php index 0e9de26..d952a85 100644 --- a/lib/InputStreamChain.php +++ b/lib/InputStreamChain.php @@ -8,7 +8,9 @@ final class InputStreamChain implements InputStream { + /** @var InputStream[] */ private $streams; + /** @var bool */ private $reading = false; public function __construct(InputStream ...$streams) diff --git a/lib/IteratorStream.php b/lib/IteratorStream.php index 5e79e8b..6fbe391 100644 --- a/lib/IteratorStream.php +++ b/lib/IteratorStream.php @@ -9,10 +9,16 @@ final class IteratorStream implements InputStream { + /** @var Iterator */ private $iterator; + /** @var \Throwable|null */ private $exception; + /** @var bool */ private $pending = false; + /** + * @psam-param Iterator $iterator + */ public function __construct(Iterator $iterator) { $this->iterator = $iterator; @@ -30,6 +36,7 @@ public function read(): Promise } $this->pending = true; + /** @var Deferred $deferred */ $deferred = new Deferred; $this->iterator->advance()->onResolve(function ($error, $hasNextElement) use ($deferred) { diff --git a/lib/LineReader.php b/lib/LineReader.php index 52a285e..48051aa 100644 --- a/lib/LineReader.php +++ b/lib/LineReader.php @@ -55,6 +55,9 @@ public function getBuffer(): string return $this->buffer; } + /** + * @return void + */ public function clearBuffer() { $this->buffer = ""; diff --git a/lib/Message.php b/lib/Message.php index fcbda18..3304623 100644 --- a/lib/Message.php +++ b/lib/Message.php @@ -38,22 +38,22 @@ class Message implements InputStream, Promise /** @var string */ private $buffer = ""; - /** @var \Amp\Deferred|null */ + /** @var Deferred|null */ private $pendingRead; - /** @var \Amp\Coroutine */ + /** @var Coroutine|null */ private $coroutine; /** @var bool True if onResolve() has been called. */ private $buffering = false; - /** @var \Amp\Deferred|null */ + /** @var Deferred|null */ private $backpressure; /** @var bool True if the iterator has completed. */ private $complete = false; - /** @var \Throwable Used to fail future reads on failure. */ + /** @var \Throwable|null Used to fail future reads on failure. */ private $error; /** diff --git a/lib/OutputBuffer.php b/lib/OutputBuffer.php index d31adfe..832dc71 100644 --- a/lib/OutputBuffer.php +++ b/lib/OutputBuffer.php @@ -8,12 +8,13 @@ class OutputBuffer implements OutputStream, Promise { - /** @var \Amp\Deferred|null */ + /** @var Deferred */ private $deferred; /** @var string */ - private $contents; + private $contents = ''; + /** @var bool */ private $closed = false; public function __construct() diff --git a/lib/ResourceInputStream.php b/lib/ResourceInputStream.php index d81d81f..4b82b48 100644 --- a/lib/ResourceInputStream.php +++ b/lib/ResourceInputStream.php @@ -14,13 +14,13 @@ final class ResourceInputStream implements InputStream { const DEFAULT_CHUNK_SIZE = 8192; - /** @var resource */ + /** @var resource|null */ private $resource; /** @var string */ private $watcher; - /** @var \Amp\Deferred|null */ + /** @var Deferred|null */ private $deferred; /** @var bool */ @@ -35,7 +35,7 @@ final class ResourceInputStream implements InputStream /** @var callable */ private $immediateCallable; - /** @var string */ + /** @var string|null */ private $immediateWatcher; /** @@ -123,6 +123,8 @@ public function read(): Promise return new Success; // Resolve with null on closed stream. } + \assert($this->resource !== null); + // Attempt a direct read, because Windows suffers from slow I/O on STDIN otherwise. if ($this->useSingleRead) { $data = @\fread($this->resource, $this->chunkSize); @@ -172,6 +174,7 @@ public function close() if ($meta && \strpos($meta["mode"], "+") !== false) { @\stream_socket_shutdown($this->resource, \STREAM_SHUT_RD); } else { + /** @psalm-suppress InvalidPropertyAssignmentValue */ @\fclose($this->resource); } } @@ -181,6 +184,8 @@ public function close() /** * Nulls reference to resource, marks stream unreadable, and succeeds any pending read with null. + * + * @return void */ private function free() { @@ -208,6 +213,9 @@ public function getResource() return $this->resource; } + /** + * @return void + */ public function setChunkSize(int $chunkSize) { $this->chunkSize = $chunkSize; @@ -216,6 +224,8 @@ public function setChunkSize(int $chunkSize) /** * References the read watcher, so the loop keeps running in case there's an active read. * + * @return void + * * @see Loop::reference() */ public function reference() @@ -230,6 +240,8 @@ public function reference() /** * Unreferences the read watcher, so the loop doesn't keep running even if there are active reads. * + * @return void + * * @see Loop::unreference() */ public function unreference() diff --git a/lib/ResourceOutputStream.php b/lib/ResourceOutputStream.php index b8e2d42..0bdaa74 100644 --- a/lib/ResourceOutputStream.php +++ b/lib/ResourceOutputStream.php @@ -16,13 +16,13 @@ final class ResourceOutputStream implements OutputStream const MAX_CONSECUTIVE_EMPTY_WRITES = 3; const LARGE_CHUNK_SIZE = 128 * 1024; - /** @var resource */ + /** @var resource|null */ private $resource; /** @var string */ private $watcher; - /** @var \SplQueue */ + /** @var \SplQueue */ private $writes; /** @var bool */ @@ -62,8 +62,8 @@ public function __construct($stream, int $chunkSize = null) try { while (!$writes->isEmpty()) { - /** @var \Amp\Deferred $deferred */ - list($data, $previous, $deferred) = $writes->shift(); + /** @var Deferred $deferred */ + [$data, $previous, $deferred] = $writes->shift(); $length = \strlen($data); if ($length === 0) { @@ -125,9 +125,10 @@ public function __construct($stream, int $chunkSize = null) $resource = null; $writable = false; + /** @psalm-suppress PossiblyUndefinedVariable */ $deferred->fail($exception); while (!$writes->isEmpty()) { - list(, , $deferred) = $writes->shift(); + [, , $deferred] = $writes->shift(); $deferred->fail($exception); } @@ -265,6 +266,7 @@ public function close() if ($meta && \strpos($meta["mode"], "+") !== false) { @\stream_socket_shutdown($this->resource, \STREAM_SHUT_WR); } else { + /** @psalm-suppress InvalidPropertyAssignmentValue psalm reports this as closed-resource */ @\fclose($this->resource); } } @@ -274,6 +276,8 @@ public function close() /** * Nulls reference to resource, marks stream unwritable, and fails any pending write. + * + * @return void */ private function free() { @@ -283,8 +287,8 @@ private function free() if (!$this->writes->isEmpty()) { $exception = new ClosedException("The socket was closed before writing completed"); do { - /** @var \Amp\Deferred $deferred */ - list(, , $deferred) = $this->writes->shift(); + /** @var Deferred $deferred */ + [, , $deferred] = $this->writes->shift(); $deferred->fail($exception); } while (!$this->writes->isEmpty()); } @@ -300,6 +304,9 @@ public function getResource() return $this->resource; } + /** + * @return void + */ public function setChunkSize(int $chunkSize) { $this->chunkSize = $chunkSize; diff --git a/lib/ZlibInputStream.php b/lib/ZlibInputStream.php index 3b5fc86..d8cc96d 100644 --- a/lib/ZlibInputStream.php +++ b/lib/ZlibInputStream.php @@ -10,9 +10,13 @@ */ final class ZlibInputStream implements InputStream { + /** @var InputStream|null */ private $source; + /** @var int */ private $encoding; + /** @var array */ private $options; + /** @var resource|null */ private $resource; /** @@ -45,9 +49,12 @@ public function read(): Promise return null; } + \assert($this->source !== null); + $data = yield $this->source->read(); // Needs a double guard, as stream might have been closed while reading + /** @psalm-suppress ParadoxicalCondition */ if ($this->resource === null) { return null; } @@ -74,7 +81,10 @@ public function read(): Promise }); } - /** @internal */ + /** + * @internal + * @return void + */ private function close() { $this->resource = null; diff --git a/lib/ZlibOutputStream.php b/lib/ZlibOutputStream.php index 6bc509d..542df1c 100644 --- a/lib/ZlibOutputStream.php +++ b/lib/ZlibOutputStream.php @@ -9,9 +9,13 @@ */ final class ZlibOutputStream implements OutputStream { + /** @var OutputStream|null */ private $destination; + /** @var int */ private $encoding; + /** @var array */ private $options; + /** @var resource|null */ private $resource; /** @@ -42,6 +46,8 @@ public function write(string $data): Promise throw new ClosedException("The stream has already been closed"); } + \assert($this->destination !== null); + $compressed = \deflate_add($this->resource, $data, \ZLIB_SYNC_FLUSH); if ($compressed === false) { @@ -65,6 +71,8 @@ public function end(string $finalData = ""): Promise throw new ClosedException("The stream has already been closed"); } + \assert($this->destination !== null); + $compressed = \deflate_add($this->resource, $finalData, \ZLIB_FINISH); if ($compressed === false) { @@ -79,7 +87,10 @@ public function end(string $finalData = ""): Promise return $promise; } - /** @internal */ + /** + * @internal + * @return void + */ private function close() { $this->resource = null; diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..9684f55 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +