From abee22dadb26a4a9f256cbd44848a81cd6a83e32 Mon Sep 17 00:00:00 2001 From: azjezz Date: Tue, 21 Dec 2021 21:28:34 +0100 Subject: [PATCH] chore(collection): throw out-of-bounds exception instead of invariant violation for invalid offset Signed-off-by: azjezz --- CHANGELOG.md | 2 + docs/component/collection.md | 8 ++-- docs/component/dict.md | 6 +-- .../AccessibleCollectionInterface.php | 16 +++---- src/Psl/Collection/CollectionInterface.php | 18 ++++---- .../Exception/ExceptionInterface.php | 11 +++++ .../Exception/OutOfBoundsException.php | 16 +++++++ src/Psl/Collection/IndexAccessInterface.php | 2 + src/Psl/Collection/Map.php | 39 ++++++++-------- src/Psl/Collection/MapInterface.php | 16 +++---- .../MutableAccessibleCollectionInterface.php | 12 ++--- .../Collection/MutableCollectionInterface.php | 14 +++--- src/Psl/Collection/MutableMap.php | 45 ++++++++++--------- src/Psl/Collection/MutableMapInterface.php | 14 +++--- src/Psl/Collection/MutableVector.php | 45 ++++++++++--------- src/Psl/Collection/MutableVectorInterface.php | 16 +++---- src/Psl/Collection/Vector.php | 39 ++++++++-------- src/Psl/Collection/VectorInterface.php | 16 +++---- src/Psl/DataStructure/Stack.php | 6 ++- src/Psl/Dict/drop.php | 6 +-- src/Psl/Dict/slice.php | 11 +---- src/Psl/Dict/take.php | 6 +-- src/Psl/Internal/Loader.php | 2 + tests/unit/Collection/AbstractMapTest.php | 16 +++---- tests/unit/Collection/AbstractVectorTest.php | 14 +++--- tests/unit/Collection/MutableMapTest.php | 10 ++--- tests/unit/Collection/MutableVectorTest.php | 10 ++--- tests/unit/Dict/TakeTest.php | 9 ---- 28 files changed, 219 insertions(+), 206 deletions(-) create mode 100644 src/Psl/Collection/Exception/ExceptionInterface.php create mode 100644 src/Psl/Collection/Exception/OutOfBoundsException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e23b31c8..373d54fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,3 +41,5 @@ * **BC** - `Psl\Result\ResultInterface` now implements `Psl\Promise\PromiseInterface` * **BC** - `Psl\Type\resource('curl')->toString()` now uses PHP built-in resource kind notation ( i.e: `resource (curl)` ) instead of generic notation ( i.e: `resource` ) * **BC** - `Psl\Str`, `Psl\Str\Byte`, and `Psl\Str\Grapheme` functions now throw `Psl\Str\Exception\OutOfBoundsException` instead of `Psl\Exception\InvaraintViolationsException` when `$offset` is out-of-bounds. +* **BC** - `Psl\Collection\IndexAccessInterface::at()` now throw `Psl\Collection\Exception\OutOfBoundsException` instead of `Psl\Exception\InvariantViolationException` if `$k` is out-of-bounds. +* **BC** - `Psl\Collection\AccessibleCollectionInterface::slice` signature has changed from `slice(int $start, int $length): static` to `slice(int $start, ?int $length = null): static` diff --git a/docs/component/collection.md b/docs/component/collection.md index f6dfb7f2..ff2119d4 100644 --- a/docs/component/collection.md +++ b/docs/component/collection.md @@ -25,9 +25,9 @@ #### `Classes` -- [Map](./../../src/Psl/Collection/Map.php#L18) -- [MutableMap](./../../src/Psl/Collection/MutableMap.php#L18) -- [MutableVector](./../../src/Psl/Collection/MutableVector.php#L17) -- [Vector](./../../src/Psl/Collection/Vector.php#L17) +- [Map](./../../src/Psl/Collection/Map.php#L20) +- [MutableMap](./../../src/Psl/Collection/MutableMap.php#L20) +- [MutableVector](./../../src/Psl/Collection/MutableVector.php#L19) +- [Vector](./../../src/Psl/Collection/Vector.php#L19) diff --git a/docs/component/dict.md b/docs/component/dict.md index 9714a3fc..389678f5 100644 --- a/docs/component/dict.md +++ b/docs/component/dict.md @@ -16,7 +16,7 @@ - [count_values](./../../src/Psl/Dict/count_values.php#L22) - [diff](./../../src/Psl/Dict/diff.php#L24) - [diff_by_key](./../../src/Psl/Dict/diff_by_key.php#L24) -- [drop](./../../src/Psl/Dict/drop.php#L27) +- [drop](./../../src/Psl/Dict/drop.php#L23) - [drop_while](./../../src/Psl/Dict/drop_while.php#L26) - [equal](./../../src/Psl/Dict/equal.php#L19) - [filter](./../../src/Psl/Dict/filter.php#L32) @@ -41,11 +41,11 @@ - [pull_with_key](./../../src/Psl/Dict/pull_with_key.php#L35) - [reindex](./../../src/Psl/Dict/reindex.php#L37) - [select_keys](./../../src/Psl/Dict/select_keys.php#L23) -- [slice](./../../src/Psl/Dict/slice.php#L31) +- [slice](./../../src/Psl/Dict/slice.php#L27) - [sort](./../../src/Psl/Dict/sort.php#L24) - [sort_by](./../../src/Psl/Dict/sort_by.php#L24) - [sort_by_key](./../../src/Psl/Dict/sort_by_key.php#L24) -- [take](./../../src/Psl/Dict/take.php#L22) +- [take](./../../src/Psl/Dict/take.php#L18) - [take_while](./../../src/Psl/Dict/take_while.php#L26) - [unique](./../../src/Psl/Dict/unique.php#L17) - [unique_by](./../../src/Psl/Dict/unique_by.php#L23) diff --git a/src/Psl/Collection/AccessibleCollectionInterface.php b/src/Psl/Collection/AccessibleCollectionInterface.php index 8e266b66..5cdfbbff 100644 --- a/src/Psl/Collection/AccessibleCollectionInterface.php +++ b/src/Psl/Collection/AccessibleCollectionInterface.php @@ -202,8 +202,8 @@ public function zip(iterable $iterable): AccessibleCollectionInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned - * `AccessibleCollectionInterface`. + * @param int<0, max> $n The last element that will be included in the returned + * `AccessibleCollectionInterface`. * * @return AccessibleCollectionInterface A `AccessibleCollectionInterface` that is a proper * subset of the current `AccessibleCollectionInterface` up @@ -239,8 +239,8 @@ public function takeWhile(callable $fn): AccessibleCollectionInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the - * first one in the returned `AccessibleCollectionInterface`. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `AccessibleCollectionInterface`. * * @return AccessibleCollectionInterface A `AccessibleCollectionInterface` that is a proper subset * of the current `AccessibleCollectionInterface` containing values @@ -278,9 +278,9 @@ public function dropWhile(callable $fn): AccessibleCollectionInterface; * The returned `AccessibleCollectionInterface` will always be a proper subset of this * `AccessibleCollectionInterface`. * - * @param int $start The starting key of this Vector to begin the returned - * `AccessibleCollectionInterface` - * @param int $length The length of the returned `AccessibleCollectionInterface` + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `AccessibleCollectionInterface` + * @param null|int<0, max> $length The length of the returned `AccessibleCollectionInterface` * * @return AccessibleCollectionInterface A `AccessibleCollectionInterface` that is a proper subset * of the current `AccessibleCollectionInterface` starting at `$start` @@ -288,5 +288,5 @@ public function dropWhile(callable $fn): AccessibleCollectionInterface; * * @psalm-mutation-free */ - public function slice(int $start, int $length): AccessibleCollectionInterface; + public function slice(int $start, ?int $length = null): AccessibleCollectionInterface; } diff --git a/src/Psl/Collection/CollectionInterface.php b/src/Psl/Collection/CollectionInterface.php index ef6fc595..bb13420c 100644 --- a/src/Psl/Collection/CollectionInterface.php +++ b/src/Psl/Collection/CollectionInterface.php @@ -31,6 +31,8 @@ public function isEmpty(): bool; * Get the number of items in the collection. * * @psalm-mutation-free + * + * @return int<0, max> */ public function count(): int; @@ -162,8 +164,8 @@ public function zip(iterable $iterable): CollectionInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned - * `CollectionInterface`. + * @param int<0, max> $n The last element that will be included in the returned + * `CollectionInterface`. * * @return CollectionInterface A `CollectionInterface` that is a proper subset of the current * `CollectionInterface` up to `n` elements. @@ -197,8 +199,8 @@ public function takeWhile(callable $fn): CollectionInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the - * first one in the returned `CollectionInterface`. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `CollectionInterface`. * * @return CollectionInterface A `CollectionInterface` that is a proper subset of the current * `CollectionInterface` containing values after the specified `n`-th element. @@ -234,9 +236,9 @@ public function dropWhile(callable $fn): CollectionInterface; * The returned `CollectionInterface` will always be a proper subset of this * `CollectionInterface`. * - * @param int $start The starting key of this Vector to begin the returned - * `CollectionInterface`. - * @param int $length The length of the returned `CollectionInterface`. + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `CollectionInterface`. + * @param int<0, max> $length The length of the returned `CollectionInterface`. * * @return CollectionInterface A `CollectionInterface` that is a proper subset of the current * `CollectionInterface` starting at `$start` up to but not including @@ -244,5 +246,5 @@ public function dropWhile(callable $fn): CollectionInterface; * * @psalm-mutation-free */ - public function slice(int $start, int $length): CollectionInterface; + public function slice(int $start, ?int $length = null): CollectionInterface; } diff --git a/src/Psl/Collection/Exception/ExceptionInterface.php b/src/Psl/Collection/Exception/ExceptionInterface.php new file mode 100644 index 00000000..8c8fd8ee --- /dev/null +++ b/src/Psl/Collection/Exception/ExceptionInterface.php @@ -0,0 +1,11 @@ + */ public function count(): int { - /** @psalm-suppress ImpureFunctionCall - conditionally pure */ - return Iter\count($this->elements); + /** @var int<0, max> */ + return count($this->elements); } /** @@ -189,7 +193,7 @@ public function jsonSerialize(): array * * @param Tk $k * - * @throws Psl\Exception\InvariantViolationException If $k is out-of-bounds. + * @throws Exception\OutOfBoundsException If $k is out-of-bounds. * * @return Tv * @@ -197,7 +201,9 @@ public function jsonSerialize(): array */ public function at(string|int $k): mixed { - Psl\invariant($this->contains($k), 'Key (%s) is out-of-bounds.', $k); + if (!array_key_exists($k, $this->elements)) { + throw Exception\OutOfBoundsException::for($k); + } return $this->elements[$k]; } @@ -211,8 +217,7 @@ public function at(string|int $k): mixed */ public function contains(int|string $k): bool { - /** @psalm-suppress ImpureFunctionCall - conditionally pure */ - return Iter\contains_key($this->elements, $k); + return array_key_exists($k, $this->elements); } /** @@ -404,10 +409,8 @@ public function zip(iterable $iterable): Map * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned - * `Map`. - * - * @throws Psl\Exception\InvariantViolationException If $n is negative. + * @param int<0, max> $n The last element that will be included in the returned + * `Map`. * * @return Map A `Map` that is a proper subset of the current * `Map` up to `n` elements. @@ -447,10 +450,8 @@ public function takeWhile(callable $fn): Map * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the - * first one in the returned `Map`. - * - * @throws Psl\Exception\InvariantViolationException If $n is negative. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `Map`. * * @return Map A `Map` that is a proper subset of the current * `Map` containing values after the specified `n`-th element. @@ -491,11 +492,9 @@ public function dropWhile(callable $fn): Map * * The returned `Map` will always be a proper subset of this `Map`. * - * @param int $start The starting key of this Vector to begin the returned - * `Map`. - * @param null|int $length The length of the returned `Map` - * - * @throws Psl\Exception\InvariantViolationException If $start or $length are negative. + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `Map`. + * @param null|int<0, max> $length The length of the returned `Map` * * @return Map A `Map` that is a proper subset of the current * `Map` starting at `$start` up to but not including the element `$start + $length`. diff --git a/src/Psl/Collection/MapInterface.php b/src/Psl/Collection/MapInterface.php index 67ddde67..f932c625 100644 --- a/src/Psl/Collection/MapInterface.php +++ b/src/Psl/Collection/MapInterface.php @@ -193,8 +193,8 @@ public function zip(iterable $iterable): MapInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned - * `MapInterface`. + * @param int<0, max> $n The last element that will be included in the returned + * `MapInterface`. * * @return MapInterface A `MapInterface` that is a proper subset of the current * `MapInterface` up to `n` elements. @@ -228,8 +228,8 @@ public function takeWhile(callable $fn): MapInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n - The last element to be skipped; the $n+1 element will be the - * first one in the returned `MapInterface`. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `MapInterface`. * * @return MapInterface - A `MapInterface` that is a proper subset of the current * `MapInterface` containing values after the specified `n`-th element. @@ -265,9 +265,9 @@ public function dropWhile(callable $fn): MapInterface; * The returned `MapInterface` will always be a proper subset of this * `MapInterface`. * - * @param int $start The starting key of this Vector to begin the returned - * `MapInterface` - * @param int $length The length of the returned `MapInterface` + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `MapInterface` + * @param null|int<0, max> $length The length of the returned `MapInterface` * * @return MapInterface - A `MapInterface` that is a proper subset of the current * `MapInterface` starting at `$start` up to but not including @@ -275,5 +275,5 @@ public function dropWhile(callable $fn): MapInterface; * * @psalm-mutation-free */ - public function slice(int $start, int $length): MapInterface; + public function slice(int $start, ?int $length = null): MapInterface; } diff --git a/src/Psl/Collection/MutableAccessibleCollectionInterface.php b/src/Psl/Collection/MutableAccessibleCollectionInterface.php index ac47a545..ae1ccf74 100644 --- a/src/Psl/Collection/MutableAccessibleCollectionInterface.php +++ b/src/Psl/Collection/MutableAccessibleCollectionInterface.php @@ -212,7 +212,7 @@ public function zip(iterable $iterable): MutableAccessibleCollectionInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned `MutableAccessibleCollectionInterface`. + * @param int<0, max> $n The last element that will be included in the returned `MutableAccessibleCollectionInterface`. * * @return MutableAccessibleCollectionInterface A `MutableAccessibleCollectionInterface` that is a proper * subset of the current `MutableAccessibleCollectionInterface` @@ -248,8 +248,8 @@ public function takeWhile(callable $fn): MutableAccessibleCollectionInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the - * first one in the returned `MutableAccessibleCollectionInterface`. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `MutableAccessibleCollectionInterface`. * * @return MutableAccessibleCollectionInterface A `MutableAccessibleCollectionInterface` that is a proper * subset of the current `MutableAccessibleCollectionInterface` @@ -287,8 +287,8 @@ public function dropWhile(callable $fn): MutableAccessibleCollectionInterface; * The returned `MutableAccessibleCollectionInterface` will always be a proper subset of this * `MutableAccessibleCollectionInterface`. * - * @param int $start The starting key of this Vector to begin the returned `MutableAccessibleCollectionInterface` - * @param int $length The length of the returned `MutableAccessibleCollectionInterface` + * @param int<0, max> $start The starting key of this Vector to begin the returned `MutableAccessibleCollectionInterface` + * @param null|int<0, max> $length The length of the returned `MutableAccessibleCollectionInterface` * * @return MutableAccessibleCollectionInterface A `MutableAccessibleCollectionInterface` that is a proper * subset of the current `MutableAccessibleCollectionInterface` @@ -297,5 +297,5 @@ public function dropWhile(callable $fn): MutableAccessibleCollectionInterface; * * @psalm-mutation-free */ - public function slice(int $start, int $length): MutableAccessibleCollectionInterface; + public function slice(int $start, ?int $length = null): MutableAccessibleCollectionInterface; } diff --git a/src/Psl/Collection/MutableCollectionInterface.php b/src/Psl/Collection/MutableCollectionInterface.php index 2732b62e..712e1aa5 100644 --- a/src/Psl/Collection/MutableCollectionInterface.php +++ b/src/Psl/Collection/MutableCollectionInterface.php @@ -131,7 +131,7 @@ public function zip(iterable $iterable): MutableCollectionInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned `MutableCollectionInterface`. + * @param int<0, max> $n The last element that will be included in the returned `MutableCollectionInterface`. * * @return MutableCollectionInterface A `MutableCollectionInterface` that is a proper * subset of the current `MutableCollectionInterface` up to `n` elements. @@ -166,8 +166,8 @@ public function takeWhile(callable $fn): MutableCollectionInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n - The last element to be skipped; the $n+1 element will be the - * first one in the returned `MutableCollectionInterface`. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `MutableCollectionInterface`. * * @return MutableCollectionInterface A `MutableCollectionInterface` that is a proper * subset of the current `MutableCollectionInterface` containing values @@ -205,9 +205,9 @@ public function dropWhile(callable $fn): MutableCollectionInterface; * The returned `MutableCollectionInterface` will always be a proper subset of this * `MutableCollectionInterface`. * - * @param int $start The starting key of this Vector to begin the returned - * `MutableCollectionInterface`. - * @param int $length The length of the returned `MutableCollectionInterface`. + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `MutableCollectionInterface`. + * @param null|int<0, max> $length The length of the returned `MutableCollectionInterface`. * * @return MutableCollectionInterface A `MutableCollectionInterface` that is a proper * subset of the current `MutableCollectionInterface` starting @@ -215,7 +215,7 @@ public function dropWhile(callable $fn): MutableCollectionInterface; * * @psalm-mutation-free */ - public function slice(int $start, int $length): MutableCollectionInterface; + public function slice(int $start, ?int $length = null): MutableCollectionInterface; /** * Removes all items from the collection. diff --git a/src/Psl/Collection/MutableMap.php b/src/Psl/Collection/MutableMap.php index 6ed20dc1..3321923d 100644 --- a/src/Psl/Collection/MutableMap.php +++ b/src/Psl/Collection/MutableMap.php @@ -4,11 +4,13 @@ namespace Psl\Collection; -use Psl; use Psl\Dict; use Psl\Iter; use Psl\Vec; +use function array_key_exists; +use function count; + /** * @template Tk of array-key * @template Tv @@ -151,11 +153,13 @@ public function isEmpty(): bool * Get the number of items in the current map. * * @psalm-mutation-free + * + * @return int<0, max> */ public function count(): int { - /** @psalm-suppress ImpureFunctionCall - conditionally pure */ - return Iter\count($this->elements); + /** @var int<0, max> */ + return count($this->elements); } /** @@ -187,7 +191,7 @@ public function jsonSerialize(): array * * @param Tk $k * - * @throws Psl\Exception\InvariantViolationException If $k is out-of-bounds. + * @throws Exception\OutOfBoundsException If $k is out-of-bounds. * * @return Tv * @@ -195,7 +199,9 @@ public function jsonSerialize(): array */ public function at(string|int $k): mixed { - Psl\invariant($this->contains($k), 'Key (%s) is out-of-bounds.', $k); + if (!array_key_exists($k, $this->elements)) { + throw Exception\OutOfBoundsException::for($k); + } return $this->elements[$k]; } @@ -209,8 +215,7 @@ public function at(string|int $k): mixed */ public function contains(int|string $k): bool { - /** @psalm-suppress ImpureFunctionCall - conditionally pure */ - return Iter\contains_key($this->elements, $k); + return array_key_exists($k, $this->elements); } /** @@ -399,10 +404,8 @@ public function zip(iterable $iterable): MutableMap * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned - * `MutableMap`. - * - * @throws Psl\Exception\InvariantViolationException If $n is negative. + * @param int<0, max> $n The last element that will be included in the returned + * `MutableMap`. * * @return MutableMap A `MutableMap` that is a proper subset of the current * `MutableMap` up to `n` elements. @@ -442,10 +445,8 @@ public function takeWhile(callable $fn): MutableMap * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the - * first one in the returned `MutableMap`. - * - * @throws Psl\Exception\InvariantViolationException If $n is negative. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `MutableMap`. * * @return MutableMap A `MutableMap` that is a proper subset of the current * `MutableMap` containing values after the specified `n`-th element. @@ -488,11 +489,9 @@ public function dropWhile(callable $fn): MutableMap * The returned `MutableMap` will always be a proper subset of this * `MutableMap`. * - * @param int $start The starting key of this Vector to begin the returned - * `MutableMap` - * @param null|int $length The length of the returned `MutableMap` - * - * @throws Psl\Exception\InvariantViolationException If $start or $len are negative. + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `MutableMap` + * @param null|int<0, max> $length The length of the returned `MutableMap` * * @return MutableMap A `MutableMap` that is a proper subset of the current * `MutableMap` starting at `$start` up to but not including the @@ -519,13 +518,15 @@ public function slice(int $start, ?int $length = null): MutableMap * @param Tk $k The key to which we will set the value * @param Tv $v The value to set * - * @throws Psl\Exception\InvariantViolationException If $k is out-of-bounds. + * @throws Exception\OutOfBoundsException If $k is out-of-bounds. * * @return MutableMap Returns itself */ public function set(int|string $k, mixed $v): MutableMap { - Psl\invariant($this->contains($k), 'Key (%s) is out-of-bounds.', $k); + if (!array_key_exists($k, $this->elements)) { + throw Exception\OutOfBoundsException::for($k); + } $this->elements[$k] = $v; diff --git a/src/Psl/Collection/MutableMapInterface.php b/src/Psl/Collection/MutableMapInterface.php index e475b0f9..63bfb266 100644 --- a/src/Psl/Collection/MutableMapInterface.php +++ b/src/Psl/Collection/MutableMapInterface.php @@ -196,7 +196,7 @@ public function zip(iterable $iterable): MutableMapInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned `MutableMapInterface`. + * @param int<0, max> $n The last element that will be included in the returned `MutableMapInterface`. * * @return MutableMapInterface A `MutableMapInterface` that is a proper subset of the current * `MutableMapInterface` up to `n` elements. @@ -229,8 +229,8 @@ public function takeWhile(callable $fn): MutableMapInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the first one in - * the returned `MutableMapInterface`. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the first one in + * the returned `MutableMapInterface`. * * @return MutableMapInterface A `MutableMapInterface` that is a proper subset of the current * `MutableMapInterface` containing values after the specified `n`-th element. @@ -266,9 +266,9 @@ public function dropWhile(callable $fn): MutableMapInterface; * The returned `MutableMapInterface` will always be a proper subset of this * `MutableMapInterface`. * - * @param int $start The starting key of this Vector to begin the returned - * `MutableMapInterface`. - * @param int $length The length of the returned `MutableMapInterface`. + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `MutableMapInterface`. + * @param null|int<0, max> $length The length of the returned `MutableMapInterface`. * * @return MutableMapInterface - A `MutableMapInterface` that is a proper subset of the current * `MutableMapInterface` starting at `$start` up to but not including @@ -276,7 +276,7 @@ public function dropWhile(callable $fn): MutableMapInterface; * * @psalm-mutation-free */ - public function slice(int $start, int $length): MutableMapInterface; + public function slice(int $start, ?int $length = null): MutableMapInterface; /** * Stores a value into the current collection with the specified key, diff --git a/src/Psl/Collection/MutableVector.php b/src/Psl/Collection/MutableVector.php index f80da5ed..28c9083d 100644 --- a/src/Psl/Collection/MutableVector.php +++ b/src/Psl/Collection/MutableVector.php @@ -4,11 +4,13 @@ namespace Psl\Collection; -use Psl; use Psl\Dict; use Psl\Iter; use Psl\Vec; +use function array_key_exists; +use function count; + /** * @template T * @@ -103,11 +105,13 @@ public function isEmpty(): bool * Get the number of items in the current `MutableVector`. * * @psalm-mutation-free + * + * @return int<0, max> */ public function count(): int { - /** @psalm-suppress ImpureFunctionCall - conditionally pure */ - return Iter\count($this->elements); + /** @var int<0, max> */ + return count($this->elements); } /** @@ -140,7 +144,7 @@ public function jsonSerialize(): array * * @param int $k * - * @throws Psl\Exception\InvariantViolationException If $k is out-of-bounds. + * @throws Exception\OutOfBoundsException If $k is out-of-bounds. * * @return T * @@ -148,7 +152,9 @@ public function jsonSerialize(): array */ public function at(string|int $k): mixed { - Psl\invariant($this->contains($k), 'Key (%s) is out-of-bounds.', $k); + if (!array_key_exists($k, $this->elements)) { + throw Exception\OutOfBoundsException::for($k); + } return $this->elements[$k]; } @@ -162,8 +168,7 @@ public function at(string|int $k): mixed */ public function contains(int|string $k): bool { - /** @psalm-suppress ImpureFunctionCall - conditionally pure */ - return Iter\contains_key($this->elements, $k); + return array_key_exists($k, $this->elements); } /** @@ -244,13 +249,15 @@ public function linearSearch(mixed $search_value): ?int * @param int $k The key to which we will set the value * @param T $v The value to set * - * @throws Psl\Exception\InvariantViolationException If $k is out-of-bounds. + * @throws Exception\OutOfBoundsException If $k is out-of-bounds. * * @return MutableVector returns itself */ public function set(int|string $k, mixed $v): MutableVector { - Psl\invariant($this->contains($k), 'Key (%s) is out-of-bounds.', $k); + if (!array_key_exists($k, $this->elements)) { + throw Exception\OutOfBoundsException::for($k); + } $this->elements[$k] = $v; @@ -501,10 +508,8 @@ public function zip(iterable $iterable): MutableVector * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned - * `MutableVector`. - * - * @throws Psl\Exception\InvariantViolationException If $n is negative. + * @param int<0, max> $n The last element that will be included in the returned + * `MutableVector`. * * @return MutableVector A `MutableVector` that is a proper subset of the current * `MutableVector` up to `n` elements. @@ -544,10 +549,8 @@ public function takeWhile(callable $fn): MutableVector * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the - * first one in the returned `MutableVector`. - * - * @throws Psl\Exception\InvariantViolationException If $n is negative. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `MutableVector`. * * @return MutableVector A `MutableVector` that is a proper subset of the current * `MutableVector` containing values after the specified `n`-th element. @@ -589,11 +592,9 @@ public function dropWhile(callable $fn): MutableVector * The returned `MutableVector` will always be a proper subset of this * `MutableVector`. * - * @param int $start The starting key of this Vector to begin the returned - * `MutableVector`. - * @param null|int $length The length of the returned `MutableVector` - * - * @throws Psl\Exception\InvariantViolationException If $start or $len are negative. + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `MutableVector`. + * @param null|int<0, max> $length The length of the returned `MutableVector` * * @return MutableVector A `MutableVector` that is a proper subset of the current * `MutableVector` starting at `$start` up to but not including diff --git a/src/Psl/Collection/MutableVectorInterface.php b/src/Psl/Collection/MutableVectorInterface.php index 6dddd6e4..0e68190a 100644 --- a/src/Psl/Collection/MutableVectorInterface.php +++ b/src/Psl/Collection/MutableVectorInterface.php @@ -205,8 +205,8 @@ public function zip(iterable $iterable): MutableVectorInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned - * `MutableVectorInterface`. + * @param int<0, max> $n The last element that will be included in the returned + * `MutableVectorInterface`. * * @return MutableVectorInterface A `MutableVectorInterface` that is a proper subset of the current * `MutableVectorInterface` up to `n` elements. @@ -240,8 +240,8 @@ public function takeWhile(callable $fn): MutableVectorInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the - * first one in the returned `MutableVectorInterface`. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `MutableVectorInterface`. * * @return MutableVectorInterface A `MutableVectorInterface` that is a proper subset of the current * `MutableVectorInterface` containing values after the specified `n`-th element. @@ -277,9 +277,9 @@ public function dropWhile(callable $fn): MutableVectorInterface; * The returned `MutableVectorInterface` will always be a proper subset of this * `MutableVectorInterface`. * - * @param int $start The starting key of this Vector to begin the returned - * `MutableVectorInterface`. - * @param int $length The length of the returned `MutableVectorInterface`. + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `MutableVectorInterface`. + * @param null|int<0, max> $length The length of the returned `MutableVectorInterface`. * * @return MutableVectorInterface A `MutableVectorInterface` that is a proper subset of the current * `MutableVectorInterface` starting at `$start` up to but not including @@ -287,7 +287,7 @@ public function dropWhile(callable $fn): MutableVectorInterface; * * @psalm-mutation-free */ - public function slice(int $start, int $length): MutableVectorInterface; + public function slice(int $start, ?int $length = null): MutableVectorInterface; /** * Stores a value into the current vector with the specified key, diff --git a/src/Psl/Collection/Vector.php b/src/Psl/Collection/Vector.php index c1c05bd5..c32d3afb 100644 --- a/src/Psl/Collection/Vector.php +++ b/src/Psl/Collection/Vector.php @@ -4,11 +4,13 @@ namespace Psl\Collection; -use Psl; use Psl\Dict; use Psl\Iter; use Psl\Vec; +use function array_key_exists; +use function count; + /** * @template T * @@ -104,11 +106,13 @@ public function isEmpty(): bool * Get the number of items in the current `Vector`. * * @psalm-mutation-free + * + * @return int<0, max> */ public function count(): int { - /** @psalm-suppress ImpureFunctionCall - conditionally pure */ - return Iter\count($this->elements); + /** @var int<0, max> */ + return count($this->elements); } /** @@ -142,7 +146,7 @@ public function jsonSerialize(): array * * @param int $k * - * @throws Psl\Exception\InvariantViolationException If $k is out-of-bounds. + * @throws Exception\OutOfBoundsException If $k is out-of-bounds. * * @return T * @@ -150,7 +154,9 @@ public function jsonSerialize(): array */ public function at(string|int $k): mixed { - Psl\invariant($this->contains($k), 'Key (%s) is out-of-bounds.', $k); + if (!array_key_exists($k, $this->elements)) { + throw Exception\OutOfBoundsException::for($k); + } return $this->elements[$k]; } @@ -164,8 +170,7 @@ public function at(string|int $k): mixed */ public function contains(int|string $k): bool { - /** @psalm-suppress ImpureFunctionCall - conditionally pure */ - return Iter\contains_key($this->elements, $k); + return array_key_exists($k, $this->elements); } /** @@ -383,10 +388,8 @@ public function zip(iterable $iterable): Vector * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param $n The last element that will be included in the returned - * `Vector`. - * - * @throws Psl\Exception\InvariantViolationException If $n is negative. + * @param int<0, max> $n The last element that will be included in the returned + * `Vector`. * * @return Vector A `Vector` that is a proper subset of the current * `Vector` up to `n` elements. @@ -426,10 +429,8 @@ public function takeWhile(callable $fn): Vector * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the - * first one in the returned `Vector`. - * - * @throws Psl\Exception\InvariantViolationException If $n is negative. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `Vector`. * * @return Vector A `Vector` that is a proper subset of the current * `Vector` containing values after the specified `n`-th element. @@ -471,11 +472,9 @@ public function dropWhile(callable $fn): Vector * The returned `Vector` will always be a proper subset of this * `Vector`. * - * @param int $start The starting key of this Vector to begin the returned - * `Vector`. - * @param null|int $length The length of the returned `Vector` - * - * @throws Psl\Exception\InvariantViolationException If $start or $len are negative. + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `Vector`. + * @param null|int<0, max> $length The length of the returned `Vector` * * @return Vector A `Vector` that is a proper subset of the current * `Vector` starting at `$start` up to but not including the diff --git a/src/Psl/Collection/VectorInterface.php b/src/Psl/Collection/VectorInterface.php index 0945a656..ecae6583 100644 --- a/src/Psl/Collection/VectorInterface.php +++ b/src/Psl/Collection/VectorInterface.php @@ -232,8 +232,8 @@ public function zip(iterable $iterable): VectorInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element that will be included in the returned - * `VectorInterface`. + * @param int<0, max> $n The last element that will be included in the returned + * `VectorInterface`. * * @return VectorInterface A `VectorInterface` that is a proper subset of the current * `VectorInterface` up to `n` elements. @@ -267,8 +267,8 @@ public function takeWhile(callable $fn): VectorInterface; * * `$n` is 1-based. So the first element is 1, the second 2, etc. * - * @param int $n The last element to be skipped; the $n+1 element will be the - * first one in the returned `VectorInterface`. + * @param int<0, max> $n The last element to be skipped; the $n+1 element will be the + * first one in the returned `VectorInterface`. * * @return VectorInterface A `VectorInterface` that is a proper subset of the current * `VectorInterface` containing values after the specified `n`-th element. @@ -304,9 +304,9 @@ public function dropWhile(callable $fn): VectorInterface; * The returned `VectorInterface` will always be a proper subset of this * `VectorInterface`. * - * @param int $start The starting key of this Vector to begin the returned - * `VectorInterface`. - * @param int $length The length of the returned `VectorInterface`. + * @param int<0, max> $start The starting key of this Vector to begin the returned + * `VectorInterface`. + * @param int<0, max> $length The length of the returned `VectorInterface`. * * @return VectorInterface A `VectorInterface` that is a proper subset of the current * `VectorInterface` starting at `$start` up to but not including @@ -314,5 +314,5 @@ public function dropWhile(callable $fn): VectorInterface; * * @psalm-mutation-free */ - public function slice(int $start, int $length): VectorInterface; + public function slice(int $start, ?int $length = null): VectorInterface; } diff --git a/src/Psl/DataStructure/Stack.php b/src/Psl/DataStructure/Stack.php index 03254b65..9c2559c3 100644 --- a/src/Psl/DataStructure/Stack.php +++ b/src/Psl/DataStructure/Stack.php @@ -71,8 +71,10 @@ public function pop(): mixed { Psl\invariant(0 !== ($i = $this->count()), 'Cannot pop an item from an empty Stack.'); - $tail = $this->items[$i - 1]; - $this->items = Vec\values(Dict\take($this->items, $this->count() - 1)); + /** @var int<0, max> $position */ + $position = $i - 1; + $tail = $this->items[$position]; + $this->items = Vec\values(Dict\take($this->items, $position)); return $tail; } diff --git a/src/Psl/Dict/drop.php b/src/Psl/Dict/drop.php index 18099e91..a29cf829 100644 --- a/src/Psl/Dict/drop.php +++ b/src/Psl/Dict/drop.php @@ -4,8 +4,6 @@ namespace Psl\Dict; -use Psl; - /** * Drops the first n items from an iterable. * @@ -18,9 +16,7 @@ * @template Tv * * @param iterable $iterable Iterable to drop the elements from - * @param int $n Number of elements to drop from the start - * - * @throws Psl\Exception\InvariantViolationException If the $n is negative + * @param int<0, max> $n Number of elements to drop from the start * * @return array */ diff --git a/src/Psl/Dict/slice.php b/src/Psl/Dict/slice.php index dcda8163..b788f8b8 100644 --- a/src/Psl/Dict/slice.php +++ b/src/Psl/Dict/slice.php @@ -4,8 +4,6 @@ namespace Psl\Dict; -use Psl; - /** * Takes a slice from an iterable. * @@ -21,18 +19,13 @@ * @template Tv * * @param iterable $iterable Iterable to take the slice from - * @param int $start Start offset - * @param int $length Length (if not specified all remaining values from the array are used) - * - * @throws Psl\Exception\InvariantViolationException If the $start offset or $length are negative + * @param int<0, max> $start Start offset + * @param null|int<0, max> $length Length (if not specified all remaining values from the array are used) * * @return array */ function slice(iterable $iterable, int $start, ?int $length = null): array { - Psl\invariant($start >= 0, 'Start offset must be non-negative.'); - Psl\invariant(null === $length || $length >= 0, 'Length must be non-negative.'); - $result = []; if (0 === $length) { return $result; diff --git a/src/Psl/Dict/take.php b/src/Psl/Dict/take.php index e8a7b19a..c13dd29b 100644 --- a/src/Psl/Dict/take.php +++ b/src/Psl/Dict/take.php @@ -4,8 +4,6 @@ namespace Psl\Dict; -use Psl; - /** * Take the first n elements from an iterable. * @@ -13,9 +11,7 @@ * @template Tv * * @param iterable $iterable Iterable to take the elements from - * @param int $n Number of elements to take from the start - * - * @throws Psl\Exception\InvariantViolationException If the $n is negative + * @param int<0, max> $n Number of elements to take from the start * * @return array */ diff --git a/src/Psl/Internal/Loader.php b/src/Psl/Internal/Loader.php index 96bdec7e..179f1c14 100644 --- a/src/Psl/Internal/Loader.php +++ b/src/Psl/Internal/Loader.php @@ -557,6 +557,7 @@ final class Loader 'Psl\Promise\PromiseInterface', 'Psl\Iter\Exception\ExceptionInterface', 'Psl\Str\Exception\ExceptionInterface', + 'Psl\Collection\Exception\ExceptionInterface', ]; public const TRAITS = [ @@ -696,6 +697,7 @@ final class Loader 'Psl\Channel\Exception\FullChannelException', 'Psl\Iter\Exception\OutOfBoundsException', 'Psl\Str\Exception\OutOfBoundsException', + 'Psl\Collection\Exception\OutOfBoundsException', ]; public const ENUMS = [ diff --git a/tests/unit/Collection/AbstractMapTest.php b/tests/unit/Collection/AbstractMapTest.php index 46f26b58..fe210c48 100644 --- a/tests/unit/Collection/AbstractMapTest.php +++ b/tests/unit/Collection/AbstractMapTest.php @@ -5,28 +5,28 @@ namespace Psl\Tests\Unit\Collection; use PHPUnit\Framework\TestCase; +use Psl\Collection; use Psl\Collection\MapInterface; use Psl\Collection\VectorInterface; -use Psl\Exception\InvariantViolationException; use Psl\Str; /** - * @covers \Psl\Collection\AbstractMap - * @covers \Psl\Collection\AbstractAccessibleCollection + * @covers \Psl\Collection\Map + * @covers \Psl\Collection\MutableMap */ abstract class AbstractMapTest extends TestCase { /** * The Map class being currently tested. * - * @var class-string + * @var class-string */ protected string $mapClass = MapInterface::class; /** * The Vector class used for values, keys .. etc. * - * @var class-string + * @var class-string */ protected string $vectorClass = VectorInterface::class; @@ -536,8 +536,8 @@ public function testAt(): void static::assertSame('hello', $map->at('foo')); static::assertSame('world', $map->at('bar')); - $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('Key (baz) is out-of-bounds.'); + $this->expectException(Collection\Exception\OutOfBoundsException::class); + $this->expectExceptionMessage('Key (baz) was out-of-bounds.'); $map->at('baz'); } @@ -572,7 +572,7 @@ public function testGet(): void * * @param iterable $items * - * @return IMap + * @return MapInterface */ abstract protected function create(iterable $items): MapInterface; } diff --git a/tests/unit/Collection/AbstractVectorTest.php b/tests/unit/Collection/AbstractVectorTest.php index c51f9846..c22c3da8 100644 --- a/tests/unit/Collection/AbstractVectorTest.php +++ b/tests/unit/Collection/AbstractVectorTest.php @@ -5,20 +5,20 @@ namespace Psl\Tests\Unit\Collection; use PHPUnit\Framework\TestCase; +use Psl\Collection; use Psl\Collection\VectorInterface; -use Psl\Exception\InvariantViolationException; use Psl\Str; /** - * @covers \Psl\Collection\AbstractVector - * @covers \Psl\Collection\AbstractAccessibleCollection + * @covers \Psl\Collection\Vector + * @covers \Psl\Collection\MutableVector */ abstract class AbstractVectorTest extends TestCase { /** * The Vector class used for values, keys .. etc. * - * @var class-string + * @var class-string */ protected string $vectorClass = VectorInterface::class; @@ -516,8 +516,8 @@ public function testAt(): void static::assertSame('hello', $vector->at(0)); static::assertSame('world', $vector->at(1)); - $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('Key (2) is out-of-bounds.'); + $this->expectException(Collection\Exception\OutOfBoundsException::class); + $this->expectExceptionMessage('Key (2) was out-of-bounds.'); $vector->at(2); } @@ -551,7 +551,7 @@ public function testGet(): void * * @param iterable $items * - * @return IVector + * @return VectorInterface */ abstract protected function create(iterable $items): VectorInterface; } diff --git a/tests/unit/Collection/MutableMapTest.php b/tests/unit/Collection/MutableMapTest.php index df16e8d2..113c1932 100644 --- a/tests/unit/Collection/MutableMapTest.php +++ b/tests/unit/Collection/MutableMapTest.php @@ -4,10 +4,10 @@ namespace Psl\Tests\Unit\Collection; +use Psl\Collection; use Psl\Collection\Map; use Psl\Collection\MutableMap; use Psl\Collection\MutableVector; -use Psl\Exception\InvariantViolationException; /** * @covers \Psl\Collection\MutableMap @@ -52,8 +52,8 @@ public function testSet(): void static::assertSame('bar', $map->at('bar')); static::assertSame('baz', $map->at('baz')); - $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('Key (qux) is out-of-bounds.'); + $this->expectException(Collection\Exception\OutOfBoundsException::class); + $this->expectExceptionMessage('Key (qux) was out-of-bounds.'); $map->set('qux', 'qux'); } @@ -78,8 +78,8 @@ public function testSetAll(): void static::assertSame('bar', $map->at('bar')); static::assertSame('baz', $map->at('baz')); - $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('Key (qux) is out-of-bounds.'); + $this->expectException(Collection\Exception\OutOfBoundsException::class); + $this->expectExceptionMessage('Key (qux) was out-of-bounds.'); $map->setAll(['qux' => 'qux']); } diff --git a/tests/unit/Collection/MutableVectorTest.php b/tests/unit/Collection/MutableVectorTest.php index dbcaa690..0fd0c444 100644 --- a/tests/unit/Collection/MutableVectorTest.php +++ b/tests/unit/Collection/MutableVectorTest.php @@ -4,10 +4,10 @@ namespace Psl\Tests\Unit\Collection; +use Psl\Collection; use Psl\Collection\Map; use Psl\Collection\MutableVector; use Psl\Collection\Vector; -use Psl\Exception\InvariantViolationException; final class MutableVectorTest extends AbstractVectorTest { @@ -46,8 +46,8 @@ public function testSet(): void static::assertSame('bar', $vector->at(1)); static::assertSame('baz', $vector->at(2)); - $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('Key (3) is out-of-bounds.'); + $this->expectException(Collection\Exception\OutOfBoundsException::class); + $this->expectExceptionMessage('Key (3) was out-of-bounds.'); $vector->set(3, 'qux'); } @@ -72,8 +72,8 @@ public function testSetAll(): void static::assertSame('bar', $vector->at(1)); static::assertSame('baz', $vector->at(2)); - $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('Key (3) is out-of-bounds.'); + $this->expectException(Collection\Exception\OutOfBoundsException::class); + $this->expectExceptionMessage('Key (3) was out-of-bounds.'); $vector->setAll([3 => 'qux']); } diff --git a/tests/unit/Dict/TakeTest.php b/tests/unit/Dict/TakeTest.php index 30158cd9..8633f4f4 100644 --- a/tests/unit/Dict/TakeTest.php +++ b/tests/unit/Dict/TakeTest.php @@ -5,7 +5,6 @@ namespace Psl\Tests\Unit\Dict; use PHPUnit\Framework\TestCase; -use Psl; use Psl\Dict; final class TakeTest extends TestCase @@ -16,12 +15,4 @@ public function testTake(): void static::assertSame([-5, -4, -3], $result); } - - public function testTakeThrowsIfLengthIsNegative(): void - { - $this->expectException(Psl\Exception\InvariantViolationException::class); - $this->expectExceptionMessage('Length must be non-negative.'); - - Dict\take([1, 2, 3], -3); - } }