From 3ae485ad113346fb9886dc14829e056c922aae30 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 13 Sep 2021 17:28:56 +0200 Subject: [PATCH 01/10] refactor: Update `Associate` typing information, add tests. --- src/Collection.php | 4 +-- src/Contract/Operation/Associateable.php | 9 ++++-- src/Operation/Associate.php | 25 ++++++++------- tests/static-analysis/associate.php | 40 ++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 tests/static-analysis/associate.php diff --git a/src/Collection.php b/src/Collection.php index 5acce6bc2..086939100 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -196,9 +196,9 @@ public function associate( ): CollectionInterface { $defaultCallback = /** - * @param T|TKey $carry + * @param mixed $carry * - * @return T|TKey + * @return mixed */ static fn ($carry) => $carry; diff --git a/src/Contract/Operation/Associateable.php b/src/Contract/Operation/Associateable.php index 3c4d28927..e8f4c6476 100644 --- a/src/Contract/Operation/Associateable.php +++ b/src/Contract/Operation/Associateable.php @@ -23,10 +23,13 @@ interface Associateable * * @see https://loophp-collection.readthedocs.io/en/stable/pages/api.html#associate * - * @param null|callable(TKey, TKey, T, Iterator):(T|TKey) $callbackForKeys - * @param null|callable(T, TKey, T, Iterator):(T|TKey) $callbackForValues + * @template NewT + * @template NewTKey * - * @return Collection + * @param callable((TKey|NewTKey)=, T=, TKey=, Iterator=): NewTKey $callbackForKeys + * @param callable((T|NewT)=, T=, TKey=, Iterator=): NewT $callbackForValues + * + * @return Collection */ public function associate(?callable $callbackForKeys = null, ?callable $callbackForValues = null): Collection; } diff --git a/src/Operation/Associate.php b/src/Operation/Associate.php index 7f3d07a50..9296a85fd 100644 --- a/src/Operation/Associate.php +++ b/src/Operation/Associate.php @@ -27,45 +27,48 @@ final class Associate extends AbstractOperation /** * @pure * - * @return Closure(callable(TKey, TKey, T, Iterator): (T|TKey) ...): Closure((callable(T, TKey, T, Iterator): (T|TKey))...): Closure(Iterator): Generator + * @template NewTKey + * @template NewT + * + * @return Closure((callable((TKey|NewTKey)=, T=, TKey=, Iterator=): NewTKey) ...): Closure((callable((T|NewT)=, T=, TKey=, Iterator=): NewT)...): Closure(Iterator): Generator */ public function __invoke(): Closure { return /** - * @param callable(TKey, TKey, T, Iterator): (T|TKey) ...$callbackForKeys + * @param callable((TKey|NewTKey)=, T=, TKey=, Iterator=): NewTKey ...$callbackForKeys * - * @return Closure((callable(T, TKey, T, Iterator): (T|TKey))...): Closure(Iterator): Generator + * @return Closure((callable((T|NewT)=, T=, TKey=, Iterator=): NewT)...): Closure(Iterator): Generator */ static fn (callable ...$callbackForKeys): Closure => /** - * @param callable(T, TKey, T, Iterator): (T|TKey) ...$callbackForValues + * @param callable((T|NewT)=, T=, TKey=, Iterator=): NewT ...$callbackForValues * - * @return Closure(Iterator): Generator + * @return Closure(Iterator): Generator */ static fn (callable ...$callbackForValues): Closure => /** * @param Iterator $iterator * - * @return Generator + * @return Generator */ static function (Iterator $iterator) use ($callbackForKeys, $callbackForValues): Generator { $callbackFactory = /** * @param TKey $key * - * @return Closure(T): Closure(T|TKey, callable(T|TKey, TKey, T, Iterator): (T|TKey), int, Iterator): (T|TKey) + * @return Closure(T): Closure(T|TKey, callable((T|TKey)=, TKey=, T=, Iterator=): (T|TKey), int, Iterator): (T|TKey) */ static fn ($key): Closure => /** * @param T $value * - * @return Closure(T|TKey, callable(T|TKey, TKey, T, Iterator): (T|TKey), int, Iterator): (T|TKey) + * @return Closure(T|TKey, callable((T|TKey)=, TKey=, T=, Iterator=): (T|TKey), int, Iterator): (T|TKey) */ static fn ($value): Closure => /** * @param T|TKey $accumulator - * @param callable(T|TKey, TKey, T, Iterator): (T|TKey) $callback + * @param callable((T|TKey)=, TKey=, T=, Iterator=): (T|TKey) $callback * @param Iterator $iterator * * @return T|TKey @@ -75,10 +78,10 @@ static function (Iterator $iterator) use ($callbackForKeys, $callbackForValues): foreach ($iterator as $key => $value) { $reduceCallback = $callbackFactory($key)($value); - /** @var Generator $k */ + /** @var Generator $k */ $k = Reduce::of()($reduceCallback)($key)(new ArrayIterator($callbackForKeys)); - /** @var Generator $c */ + /** @var Generator $c */ $c = Reduce::of()($reduceCallback)($value)(new ArrayIterator($callbackForValues)); yield $k->current() => $c->current(); diff --git a/tests/static-analysis/associate.php b/tests/static-analysis/associate.php new file mode 100644 index 000000000..a8051c4fe --- /dev/null +++ b/tests/static-analysis/associate.php @@ -0,0 +1,40 @@ + $collection + */ +function associate_checkIntInt(CollectionInterface $collection): void +{ +} +/** + * @param CollectionInterface $collection + */ +function associate_checkStringString(CollectionInterface $collection): void +{ +} +/** + * @param CollectionInterface $collection + */ +function associate_checkStringBool(CollectionInterface $collection): void +{ +} + +$square = static fn (int $val): int => $val ** 2; +$toBoldString = static fn (int $val): string => sprintf('*%s*', $val); +$isEven = static fn (int $val): bool => 0 === $val % 2; + +associate_checkIntInt(Collection::fromIterable([1, 2, 3])->associate($square, $square)); +associate_checkStringString(Collection::fromIterable([1, 2, 3])->associate($toBoldString, $toBoldString)); +associate_checkStringBool(Collection::fromIterable([1, 2, 3])->associate($toBoldString, $isEven)); From e2ef673d8b41b0f37e1a0cf1701bd2b2026859df Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 13 Sep 2021 17:44:46 +0200 Subject: [PATCH 02/10] refactor: Update `Zip` typing information, add tests. --- src/Contract/Operation/Zipable.php | 7 +++-- src/Operation/Zip.php | 21 ++++++++------- tests/static-analysis/zip.php | 43 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 tests/static-analysis/zip.php diff --git a/src/Contract/Operation/Zipable.php b/src/Contract/Operation/Zipable.php index 4ff46d003..ec401ca38 100644 --- a/src/Contract/Operation/Zipable.php +++ b/src/Contract/Operation/Zipable.php @@ -22,9 +22,12 @@ interface Zipable * * @see https://loophp-collection.readthedocs.io/en/stable/pages/api.html#zip * - * @param iterable ...$iterables + * @template U + * @template UKey * - * @return Collection, list> + * @param iterable ...$iterables + * + * @return Collection, list> */ public function zip(iterable ...$iterables): Collection; } diff --git a/src/Operation/Zip.php b/src/Operation/Zip.php index 735c19b14..76b08a62e 100644 --- a/src/Operation/Zip.php +++ b/src/Operation/Zip.php @@ -29,34 +29,37 @@ final class Zip extends AbstractOperation /** * @pure * - * @return Closure(iterable...): Closure(Iterator): Iterator, list> + * @template UKey + * @template U + * + * @return Closure(iterable...): Closure(Iterator): Iterator, list> */ public function __invoke(): Closure { return /** - * @param iterable ...$iterables + * @param iterable ...$iterables * - * @return Closure(Iterator): Iterator, list> + * @return Closure(Iterator): Iterator, list> */ static function (iterable ...$iterables): Closure { $buildArrayIterator = /** - * @param list> $iterables + * @param list> $iterables */ static fn (array $iterables): Closure => /** * @param Iterator $iterator * - * @return ArrayIterator|IterableIterator)> + * @return ArrayIterator|IterableIterator)> */ static fn (Iterator $iterator): Iterator => new ArrayIterator([ $iterator, ...array_map( /** - * @param iterable $iterable + * @param iterable $iterable * - * @return IterableIterator + * @return IterableIterator */ static fn (iterable $iterable): IterableIterator => new IterableIterator($iterable), $iterables @@ -65,7 +68,7 @@ static function (iterable ...$iterables): Closure { $buildMultipleIterator = /** - * @return Closure(ArrayIterator|IterableIterator)>): MultipleIterator + * @return Closure(ArrayIterator|IterableIterator)>): MultipleIterator */ Reduce::of()( /** @@ -78,7 +81,7 @@ static function (MultipleIterator $acc, Iterator $iterator): MultipleIterator { } )(new MultipleIterator(MultipleIterator::MIT_NEED_ANY)); - /** @var Closure(Iterator): Generator, list> $pipe */ + /** @var Closure(Iterator): Generator, list> $pipe */ $pipe = Pipe::of()( $buildArrayIterator($iterables), $buildMultipleIterator, diff --git a/tests/static-analysis/zip.php b/tests/static-analysis/zip.php new file mode 100644 index 000000000..7505dbe20 --- /dev/null +++ b/tests/static-analysis/zip.php @@ -0,0 +1,43 @@ +, list> $collection + */ +function zip_checkIntString(CollectionInterface $collection): void +{ +} + +zip_checkIntString(Collection::fromIterable(range(1, 3))->zip(range('a', 'c'))); + +/** + * @param CollectionInterface, list> $collection + */ +function zip_checkBoolString(CollectionInterface $collection): void +{ +} + +$generator = + /** + * @return Generator + */ + static function (): Generator { + yield true => true; + + yield false => false; + + yield true => true; + }; + +zip_checkBoolString(Collection::fromIterable($generator())->zip(range('a', 'c'))); From 673f3ee6bda6d0a798fb4030d87c6d30d86418b9 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 13 Sep 2021 18:52:31 +0200 Subject: [PATCH 03/10] refactor: `Associate` operation is not more variadic. --- spec/loophp/collection/CollectionSpec.php | 4 +- src/Contract/Operation/Associateable.php | 4 +- src/Operation/Associate.php | 45 ++++------------------- src/Operation/Flip.php | 7 ++-- src/Operation/Pair.php | 7 +--- src/Operation/Transpose.php | 7 +--- src/Operation/Unpack.php | 5 +-- 7 files changed, 20 insertions(+), 59 deletions(-) diff --git a/spec/loophp/collection/CollectionSpec.php b/spec/loophp/collection/CollectionSpec.php index 5aeffa3e8..515823a68 100644 --- a/spec/loophp/collection/CollectionSpec.php +++ b/spec/loophp/collection/CollectionSpec.php @@ -225,10 +225,10 @@ public function it_can_associate(): void $this::fromIterable($input) ->associate( - static function (int $carry, int $key, int $value): int { + static function (int $key, int $value): int { return $key * 2; }, - static function (int $carry, int $key, int $value): int { + static function (int $value, int $key): int { return $value * 2; } ) diff --git a/src/Contract/Operation/Associateable.php b/src/Contract/Operation/Associateable.php index e8f4c6476..d54a4a3e3 100644 --- a/src/Contract/Operation/Associateable.php +++ b/src/Contract/Operation/Associateable.php @@ -26,8 +26,8 @@ interface Associateable * @template NewT * @template NewTKey * - * @param callable((TKey|NewTKey)=, T=, TKey=, Iterator=): NewTKey $callbackForKeys - * @param callable((T|NewT)=, T=, TKey=, Iterator=): NewT $callbackForValues + * @param callable(TKey=, T=, Iterator=): NewTKey $callbackForKeys + * @param callable(T=, TKey=, Iterator=): NewT $callbackForValues * * @return Collection */ diff --git a/src/Operation/Associate.php b/src/Operation/Associate.php index 9296a85fd..6be83aba1 100644 --- a/src/Operation/Associate.php +++ b/src/Operation/Associate.php @@ -9,7 +9,6 @@ namespace loophp\collection\Operation; -use ArrayIterator; use Closure; use Generator; use Iterator; @@ -30,61 +29,31 @@ final class Associate extends AbstractOperation * @template NewTKey * @template NewT * - * @return Closure((callable((TKey|NewTKey)=, T=, TKey=, Iterator=): NewTKey) ...): Closure((callable((T|NewT)=, T=, TKey=, Iterator=): NewT)...): Closure(Iterator): Generator + * @return Closure(callable(TKey=, T=, Iterator=): NewTKey): Closure(callable(T=, TKey=, Iterator=): NewT): Closure(Iterator): Generator */ public function __invoke(): Closure { return /** - * @param callable((TKey|NewTKey)=, T=, TKey=, Iterator=): NewTKey ...$callbackForKeys + * @param callable(TKey=, T=, Iterator=): NewTKey $callbackForKeys * - * @return Closure((callable((T|NewT)=, T=, TKey=, Iterator=): NewT)...): Closure(Iterator): Generator + * @return Closure((callable(T=, TKey=, Iterator=): NewT)): Closure(Iterator): Generator */ - static fn (callable ...$callbackForKeys): Closure => + static fn (callable $callbackForKeys): Closure => /** - * @param callable((T|NewT)=, T=, TKey=, Iterator=): NewT ...$callbackForValues + * @param callable(T=, TKey=, Iterator=): NewT $callbackForValues * * @return Closure(Iterator): Generator */ - static fn (callable ...$callbackForValues): Closure => + static fn (callable $callbackForValues): Closure => /** * @param Iterator $iterator * * @return Generator */ static function (Iterator $iterator) use ($callbackForKeys, $callbackForValues): Generator { - $callbackFactory = - /** - * @param TKey $key - * - * @return Closure(T): Closure(T|TKey, callable((T|TKey)=, TKey=, T=, Iterator=): (T|TKey), int, Iterator): (T|TKey) - */ - static fn ($key): Closure => - /** - * @param T $value - * - * @return Closure(T|TKey, callable((T|TKey)=, TKey=, T=, Iterator=): (T|TKey), int, Iterator): (T|TKey) - */ - static fn ($value): Closure => - /** - * @param T|TKey $accumulator - * @param callable((T|TKey)=, TKey=, T=, Iterator=): (T|TKey) $callback - * @param Iterator $iterator - * - * @return T|TKey - */ - static fn ($accumulator, callable $callback, int $callbackId, Iterator $iterator) => $callback($accumulator, $key, $value, $iterator); - foreach ($iterator as $key => $value) { - $reduceCallback = $callbackFactory($key)($value); - - /** @var Generator $k */ - $k = Reduce::of()($reduceCallback)($key)(new ArrayIterator($callbackForKeys)); - - /** @var Generator $c */ - $c = Reduce::of()($reduceCallback)($value)(new ArrayIterator($callbackForValues)); - - yield $k->current() => $c->current(); + yield $callbackForKeys($key, $value, $iterator) => $callbackForValues($value, $key, $iterator); } }; } diff --git a/src/Operation/Flip.php b/src/Operation/Flip.php index 036810016..098fcb9be 100644 --- a/src/Operation/Flip.php +++ b/src/Operation/Flip.php @@ -30,22 +30,21 @@ public function __invoke(): Closure { $callbackForKeys = /** - * @param mixed $carry * @param TKey $key * @param T $value * * @return T */ - static fn ($carry, $key, $value) => $value; + static fn ($key, $value) => $value; $callbackForValues = /** - * @param mixed $carry + * @param T $value * @param TKey $key * * @return TKey */ - static fn ($carry, $key) => $key; + static fn ($value, $key) => $key; /** @var Closure(Iterator): Generator $associate */ $associate = Associate::of()($callbackForKeys)($callbackForValues); diff --git a/src/Operation/Pair.php b/src/Operation/Pair.php index a17527e90..d664f943d 100644 --- a/src/Operation/Pair.php +++ b/src/Operation/Pair.php @@ -30,23 +30,20 @@ public function __invoke(): Closure { $callbackForKeys = /** - * @param T $initial * @param TKey $key * @param array{0: TKey, 1: T} $value * * @return TKey|null */ - static fn ($initial, $key, array $value) => $value[0] ?? null; + static fn ($key, array $value) => $value[0] ?? null; $callbackForValues = /** - * @param T $initial - * @param TKey $key * @param array{0: TKey, 1: T} $value * * @return T|null */ - static fn ($initial, $key, array $value) => $value[1] ?? null; + static fn (array $value) => $value[1] ?? null; /** @var Closure(Iterator): Generator $pipe */ $pipe = Pipe::of()( diff --git a/src/Operation/Transpose.php b/src/Operation/Transpose.php index 4605d21b5..efa2930a2 100644 --- a/src/Operation/Transpose.php +++ b/src/Operation/Transpose.php @@ -34,22 +34,19 @@ public function __invoke(): Closure { $callbackForKeys = /** - * @param array $carry * @param non-empty-array $key * * @return TKey */ - static fn (array $carry, array $key) => reset($key); + static fn (array $key) => reset($key); $callbackForValues = /** - * @param array $carry - * @param array $key * @param array $value * * @return array */ - static fn (array $carry, array $key, array $value): array => $value; + static fn (array $value): array => $value; /** @var Closure(Iterator): Generator> $pipe */ $pipe = Pipe::of()( diff --git a/src/Operation/Unpack.php b/src/Operation/Unpack.php index fc757f80f..dc7f6be79 100644 --- a/src/Operation/Unpack.php +++ b/src/Operation/Unpack.php @@ -41,16 +41,15 @@ public function __invoke(): Closure * * @return NewTKey */ - static fn ($initial, int $key, array $value) => $value[0]; + static fn (int $key, array $value) => $value[0]; $callbackForValues = /** - * @param NewT $initial * @param T $value * * @return NewT */ - static fn ($initial, int $key, array $value) => $value[1]; + static fn (array $value) => $value[1]; /** @var Closure(Iterator): Generator $pipe */ $pipe = Pipe::of()( From b722eb90133e2791a04881c97af018b1de037e9a Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 13 Sep 2021 19:01:02 +0200 Subject: [PATCH 04/10] docs: Update `Associate` documentation and examples. --- docs/pages/api.rst | 28 +-------- docs/pages/code/operations/associate.php | 72 ++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 26 deletions(-) create mode 100644 docs/pages/code/operations/associate.php diff --git a/docs/pages/api.rst b/docs/pages/api.rst index dfd43f05a..35b66d100 100644 --- a/docs/pages/api.rst +++ b/docs/pages/api.rst @@ -287,32 +287,8 @@ Interface: `Associateable`_ Signature: ``Collection::associate(?callable $callbackForKeys = null, ?callable $callbackForValues = null): Collection;`` -.. code-block:: php - - $input = range(1, 10); - - Collection::fromIterable($input) - ->associate( - static function ($key, $value) { - return $key * 2; - }, - static function ($key, $value) { - return $value * 2; - } - ); - - // [ - // 0 => 2, - // 2 => 4, - // 4 => 6, - // 6 => 8, - // 8 => 10, - // 10 => 12, - // 12 => 14, - // 14 => 16, - // 16 => 18, - // 18 => 20, - // ] +.. literalinclude:: code/operations/associate.php + :language: php asyncMap ~~~~~~~~ diff --git a/docs/pages/code/operations/associate.php b/docs/pages/code/operations/associate.php new file mode 100644 index 000000000..1ba88fcd5 --- /dev/null +++ b/docs/pages/code/operations/associate.php @@ -0,0 +1,72 @@ +associate( + static function ($key, $value) { + return $key * 2; + }, + static function ($value, $key) { + return $value * 3; + } + ); + +// [ +// 0 => 3, +// 2 => 6, +// 4 => 9, +// 6 => 12, +// 8 => 15, +// ] + +// Example 2: Only the callback for keys is provided +$input = range(1, 5); + +Collection::fromIterable($input) + ->associate( + static function ($key, $value) { + return $key * 2; + } + ); + +// [ +// 0 => 1, +// 2 => 2, +// 4 => 3, +// 6 => 4, +// 8 => 5, +// ] + +// Example 3: Only the callback for values is provided +$input = range(1, 5); + +Collection::fromIterable($input) + ->associate( + null, + static function ($value, $key) { + return $value * 3; + } + ); + +// [ +// 0 => 3, +// 1 => 6, +// 2 => 9, +// 3 => 12, +// 4 => 15, +// ] From 40ce120a9bb683700dcfb19bc7d04583bac3447f Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 13 Sep 2021 20:56:12 +0200 Subject: [PATCH 05/10] Fix various minor SA typing information. Found those small glitches using PHPStan upcoming features. --- src/Collection.php | 6 ++-- src/Contract/Collection.php | 2 +- src/Iterator/ClosureIterator.php | 6 ++-- src/Iterator/ResourceIterator.php | 2 +- src/Iterator/TypedIterator.php | 12 +++++-- src/Operation/Normalize.php | 4 +-- src/Operation/Reverse.php | 2 +- src/Operation/Unpair.php | 4 +-- src/Operation/Unwords.php | 2 +- tests/static-analysis/unpack.php | 56 +++++++++++++++++++++++++++++++ 10 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 tests/static-analysis/unpack.php diff --git a/src/Collection.php b/src/Collection.php index 086939100..6a6b80fdb 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -154,7 +154,7 @@ final class Collection implements CollectionInterface { /** - * @var iterable + * @var iterable */ private iterable $parameters; @@ -167,7 +167,7 @@ final class Collection implements CollectionInterface * @psalm-external-mutation-free * * @param callable(mixed ...$parameters): iterable $callable - * @param iterable $parameters + * @param iterable $parameters */ private function __construct(callable $callable, iterable $parameters = []) { @@ -449,7 +449,7 @@ public function frequency(): CollectionInterface * @template NewT * * @param callable(mixed ...$parameters): iterable $callable - * @param iterable $parameters + * @param iterable $parameters * * @return self */ diff --git a/src/Contract/Collection.php b/src/Contract/Collection.php index a592affce..c77ea7d47 100644 --- a/src/Contract/Collection.php +++ b/src/Contract/Collection.php @@ -235,7 +235,7 @@ * @template-extends Transposeable * @template-extends Truthyable * @template-extends Unlinesable - * @template-extends Unpackable + * @template-extends Unpackable * @template-extends Unpairable * @template-extends Untilable * @template-extends Unwindowable diff --git a/src/Iterator/ClosureIterator.php b/src/Iterator/ClosureIterator.php index 0857e98e1..79d86662f 100644 --- a/src/Iterator/ClosureIterator.php +++ b/src/Iterator/ClosureIterator.php @@ -33,7 +33,7 @@ final class ClosureIterator extends ProxyIterator /** * @param callable(mixed ...$parameters): iterable $callable - * @param iterable $parameters + * @param iterable $parameters */ public function __construct(callable $callable, iterable $parameters) { @@ -48,10 +48,10 @@ public function rewind(): void } /** - * @return Generator + * @return Generator */ private function getGenerator(): Generator { - return yield from ($this->callable)(...$this->parameters); + yield from ($this->callable)(...$this->parameters); } } diff --git a/src/Iterator/ResourceIterator.php b/src/Iterator/ResourceIterator.php index 24bfe800a..0172eb7cd 100644 --- a/src/Iterator/ResourceIterator.php +++ b/src/Iterator/ResourceIterator.php @@ -38,7 +38,7 @@ public function __construct($resource, bool $closeResource = false) /** * @param resource $resource * - * @return Generator + * @return Generator */ static function ($resource) use ($closeResource): Generator { try { diff --git a/src/Iterator/TypedIterator.php b/src/Iterator/TypedIterator.php index 29ebfbb0c..cc593b1dc 100644 --- a/src/Iterator/TypedIterator.php +++ b/src/Iterator/TypedIterator.php @@ -28,7 +28,7 @@ final class TypedIterator extends ProxyIterator { /** * @param Iterator $iterator - * @param null|callable(mixed): string $getType + * @param callable(T): string $getType */ public function __construct(Iterator $iterator, ?callable $getType = null) { @@ -55,7 +55,13 @@ static function ($variable): string { }; $this->iterator = new ClosureIterator( - static function (Iterator $iterator) use ($getType): Generator { + /** + * @param Iterator $iterator + * @param callable(T): string $getType + * + * @return Generator + */ + static function (Iterator $iterator, callable $getType): Generator { $previousType = null; foreach ($iterator as $key => $value) { @@ -81,7 +87,7 @@ static function (Iterator $iterator) use ($getType): Generator { yield $key => $value; } }, - [$iterator] + [$iterator, $getType] ); } } diff --git a/src/Operation/Normalize.php b/src/Operation/Normalize.php index 2c0f994ec..98e85fde3 100644 --- a/src/Operation/Normalize.php +++ b/src/Operation/Normalize.php @@ -24,7 +24,7 @@ final class Normalize extends AbstractOperation /** * @pure * - * @return Closure(Iterator): Generator + * @return Closure(Iterator): Generator */ public function __invoke(): Closure { @@ -32,7 +32,7 @@ public function __invoke(): Closure /** * @param Iterator $iterator * - * @return Generator + * @return Generator */ static function (Iterator $iterator): Generator { foreach ($iterator as $value) { diff --git a/src/Operation/Reverse.php b/src/Operation/Reverse.php index 1d2dcd2af..cb895bf29 100644 --- a/src/Operation/Reverse.php +++ b/src/Operation/Reverse.php @@ -37,7 +37,7 @@ public function __invoke(): Closure */ static fn (array $carry, array $value): array => [...$value, ...$carry]; - /** @var Closure(Iterator): Generator $pipe */ + /** @var Closure(Iterator): Generator $pipe */ $pipe = Pipe::of()( (new Pack())(), Reduce::of()($callback)([]), diff --git a/src/Operation/Unpair.php b/src/Operation/Unpair.php index 1ae45e183..b0f71ed56 100644 --- a/src/Operation/Unpair.php +++ b/src/Operation/Unpair.php @@ -24,7 +24,7 @@ final class Unpair extends AbstractOperation /** * @pure * - * @return Closure(Iterator): Generator + * @return Closure(Iterator): Generator */ public function __invoke(): Closure { @@ -32,7 +32,7 @@ public function __invoke(): Closure /** * @param Iterator $iterator * - * @return Generator + * @return Generator */ static function (Iterator $iterator): Generator { foreach ($iterator as $key => $value) { diff --git a/src/Operation/Unwords.php b/src/Operation/Unwords.php index 80575b566..41a056960 100644 --- a/src/Operation/Unwords.php +++ b/src/Operation/Unwords.php @@ -28,7 +28,7 @@ final class Unwords extends AbstractOperation */ public function __invoke(): Closure { - /** @var Closure(Iterator): Generator $implode */ + /** @var Closure(Iterator): Generator $implode */ $implode = Implode::of()(' '); // Point free style. diff --git a/tests/static-analysis/unpack.php b/tests/static-analysis/unpack.php new file mode 100644 index 000000000..1c52eda66 --- /dev/null +++ b/tests/static-analysis/unpack.php @@ -0,0 +1,56 @@ + $collection + */ +function unpack_checkListInt(CollectionInterface $collection): void +{ +} + +/** + * @param CollectionInterface $collection + */ +function unpack_checkListString(CollectionInterface $collection): void +{ +} + +/** + * @param CollectionInterface $collection + */ +function unpack_checkListStringWithString(CollectionInterface $collection): void +{ +} + +$infiniteIntIntGenerator = static function (): Generator { + while (true) { + yield [random_int(-10, 10), random_int(-10, 10)]; + } +}; + +$infiniteIntStringGenerator = static function (): Generator { + while (true) { + yield [random_int(-10, 10), chr(random_int(0, 255))]; + } +}; + +$infiniteStringStringGenerator = static function (): Generator { + while (true) { + yield [chr(random_int(0, 255)), chr(random_int(0, 255))]; + } +}; + +unpack_checkListInt(Collection::fromIterable($infiniteIntIntGenerator())->unpack()); +unpack_checkListString(Collection::fromIterable($infiniteIntStringGenerator())->unpack()); +unpack_checkListStringWithString(Collection::fromIterable($infiniteStringStringGenerator())->unpack()); From 2f81bcef32da65f3d5db5f80824daf4ec2f428af Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 14 Sep 2021 08:30:33 +0200 Subject: [PATCH 06/10] refactor: Replace `IteratorIterator` with ClosureIterator. --- src/Iterator/ResourceIterator.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Iterator/ResourceIterator.php b/src/Iterator/ResourceIterator.php index 0172eb7cd..df0663009 100644 --- a/src/Iterator/ResourceIterator.php +++ b/src/Iterator/ResourceIterator.php @@ -11,7 +11,6 @@ use Generator; use InvalidArgumentException; -use IteratorIterator; use function is_resource; @@ -40,7 +39,7 @@ public function __construct($resource, bool $closeResource = false) * * @return Generator */ - static function ($resource) use ($closeResource): Generator { + static function ($resource, bool $closeResource): Generator { try { while (false !== $chunk = fgetc($resource)) { yield $chunk; @@ -52,6 +51,6 @@ static function ($resource) use ($closeResource): Generator { } }; - $this->iterator = new IteratorIterator($callback($resource)); + $this->iterator = new ClosureIterator($callback, [$resource, $closeResource]); } } From dafa9b668a2fc82db230e8d2dbf948c5847452b8 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Mon, 13 Sep 2021 21:29:04 +0200 Subject: [PATCH 07/10] Update PHPStan baseline - unable to get rid of the issue yet. --- phpstan-unsupported-baseline.neon | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/phpstan-unsupported-baseline.neon b/phpstan-unsupported-baseline.neon index 78136fca4..e3f3950ec 100644 --- a/phpstan-unsupported-baseline.neon +++ b/phpstan-unsupported-baseline.neon @@ -6,7 +6,7 @@ parameters: path: src/Collection.php - - message: "#^Generic type loophp\\\\collection\\\\Contract\\\\Operation\\\\Unpackable\\ in PHPDoc tag @extends does not specify all template types of interface loophp\\\\collection\\\\Contract\\\\Operation\\\\Unpackable\\: TKey, T, NewTKey, NewT$#" + message: "#^Generic type loophp\\\\collection\\\\Contract\\\\Operation\\\\Unpackable\\\\> in PHPDoc tag @extends does not specify all template types of interface loophp\\\\collection\\\\Contract\\\\Operation\\\\Unpackable\\: TKey, T, NewTKey, NewT$#" count: 1 path: src/Contract/Collection.php @@ -20,7 +20,18 @@ parameters: count: 1 path: src/Contract/Operation/Unpackable.php + - + message: "#^Parameter \\#1 \\$callable of class loophp\\\\collection\\\\Iterator\\\\ClosureIterator constructor expects callable\\(\\.\\.\\.mixed\\)\\: iterable\\, Closure\\(mixed, bool\\)\\: Generator\\ given\\.$#" + count: 1 + path: src/Iterator/ResourceIterator.php + + - + message: "#^Parameter \\#1 \\$callable of class loophp\\\\collection\\\\Iterator\\\\ClosureIterator constructor expects callable\\(\\.\\.\\.mixed\\)\\: iterable, Closure\\(Iterator, callable\\(\\)\\: mixed\\)\\: Generator\\ given\\.$#" + count: 1 + path: src/Iterator/TypedIterator.php + - message: "#^PHPDoc tag @template T for class loophp\\\\collection\\\\Operation\\\\Unpack with bound type array\\ is not supported\\.$#" count: 1 path: src/Operation/Unpack.php + From b4cdd4ada88fcb7b292ea7ff30a551b1b1f5dcc7 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 14 Sep 2021 18:56:29 +0200 Subject: [PATCH 08/10] tests: SA tests of `Zip` operation - add more tests. --- tests/static-analysis/zip.php | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/static-analysis/zip.php b/tests/static-analysis/zip.php index 7505dbe20..f6df67748 100644 --- a/tests/static-analysis/zip.php +++ b/tests/static-analysis/zip.php @@ -13,14 +13,12 @@ use loophp\collection\Contract\Collection as CollectionInterface; /** - * @param CollectionInterface, list> $collection + * @param CollectionInterface, list> $collection */ -function zip_checkIntString(CollectionInterface $collection): void +function zip_checkBoolBool(CollectionInterface $collection): void { } -zip_checkIntString(Collection::fromIterable(range(1, 3))->zip(range('a', 'c'))); - /** * @param CollectionInterface, list> $collection */ @@ -28,6 +26,20 @@ function zip_checkBoolString(CollectionInterface $collection): void { } +/** + * @param CollectionInterface, list> $collection + */ +function zip_checkIntString(CollectionInterface $collection): void +{ +} + +/** + * @param CollectionInterface, list> $collection + */ +function zip_checkBoolStringInt(CollectionInterface $collection): void +{ +} + $generator = /** * @return Generator @@ -40,4 +52,15 @@ static function (): Generator { yield true => true; }; +// With one single parameter +zip_checkBoolBool(Collection::fromIterable($generator())->zip($generator())); zip_checkBoolString(Collection::fromIterable($generator())->zip(range('a', 'c'))); +zip_checkIntString(Collection::fromIterable(range(1, 3))->zip(range('a', 'c'))); + +// With two parameters of the same types +zip_checkBoolBool(Collection::fromIterable($generator())->zip($generator(), $generator())); + +// With two parameters of different types +// Fails with PHPStan, not in PSalm. +/** @phpstan-ignore-next-line */ +zip_checkBoolStringInt(Collection::fromIterable($generator())->zip(range('a', 'c'), range(1, 3))); From b44918896d609ea0c508545ee7606319867322d6 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 14 Sep 2021 23:52:39 +0200 Subject: [PATCH 09/10] Update src/Iterator/TypedIterator.php Co-authored-by: Alex Gidei <34811569+AlexandruGG@users.noreply.github.com> --- src/Iterator/TypedIterator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Iterator/TypedIterator.php b/src/Iterator/TypedIterator.php index cc593b1dc..803206cff 100644 --- a/src/Iterator/TypedIterator.php +++ b/src/Iterator/TypedIterator.php @@ -28,7 +28,7 @@ final class TypedIterator extends ProxyIterator { /** * @param Iterator $iterator - * @param callable(T): string $getType + * @param null|callable(T): string $getType */ public function __construct(Iterator $iterator, ?callable $getType = null) { From 0b8d1de2a595762752a93e820c0a32882725f640 Mon Sep 17 00:00:00 2001 From: AlexandruGG Date: Wed, 15 Sep 2021 21:00:30 +0100 Subject: [PATCH 10/10] Fix PHPStan unsupported errors --- phpstan-unsupported-baseline.neon | 16 ---------------- src/Collection.php | 21 ++++----------------- src/Iterator/ResourceIterator.php | 4 ++-- src/Iterator/TypedIterator.php | 9 ++++++--- 4 files changed, 12 insertions(+), 38 deletions(-) diff --git a/phpstan-unsupported-baseline.neon b/phpstan-unsupported-baseline.neon index e3f3950ec..3311886b0 100644 --- a/phpstan-unsupported-baseline.neon +++ b/phpstan-unsupported-baseline.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Parameter \\#1 \\$callable of class loophp\\\\collection\\\\Collection constructor expects callable\\(\\.\\.\\.mixed\\)\\: iterable\\, Closure\\(string, string\\)\\: loophp\\\\collection\\\\Iterator\\\\StringIterator\\ given\\.$#" - count: 1 - path: src/Collection.php - - message: "#^Generic type loophp\\\\collection\\\\Contract\\\\Operation\\\\Unpackable\\\\> in PHPDoc tag @extends does not specify all template types of interface loophp\\\\collection\\\\Contract\\\\Operation\\\\Unpackable\\: TKey, T, NewTKey, NewT$#" count: 1 @@ -20,18 +15,7 @@ parameters: count: 1 path: src/Contract/Operation/Unpackable.php - - - message: "#^Parameter \\#1 \\$callable of class loophp\\\\collection\\\\Iterator\\\\ClosureIterator constructor expects callable\\(\\.\\.\\.mixed\\)\\: iterable\\, Closure\\(mixed, bool\\)\\: Generator\\ given\\.$#" - count: 1 - path: src/Iterator/ResourceIterator.php - - - - message: "#^Parameter \\#1 \\$callable of class loophp\\\\collection\\\\Iterator\\\\ClosureIterator constructor expects callable\\(\\.\\.\\.mixed\\)\\: iterable, Closure\\(Iterator, callable\\(\\)\\: mixed\\)\\: Generator\\ given\\.$#" - count: 1 - path: src/Iterator/TypedIterator.php - - message: "#^PHPDoc tag @template T for class loophp\\\\collection\\\\Operation\\\\Unpack with bound type array\\ is not supported\\.$#" count: 1 path: src/Operation/Unpack.php - diff --git a/src/Collection.php b/src/Collection.php index 6a6b80fdb..f85aca767 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -466,8 +466,7 @@ public static function fromCallable(callable $callable, iterable $parameters = [ public static function fromFile(string $filepath): self { return new self( - static fn (string $filepath): Iterator => new ResourceIterator(fopen($filepath, 'rb'), true), - [$filepath] + static fn (): Iterator => new ResourceIterator(fopen($filepath, 'rb'), true), ); } @@ -508,10 +507,7 @@ static function (Generator $generator): Generator { */ public static function fromIterable(iterable $iterable): self { - return new self( - static fn (iterable $iterable): Iterator => new IterableIterator($iterable), - [$iterable] - ); + return new self(static fn (): Iterator => new IterableIterator($iterable)); } /** @@ -523,13 +519,7 @@ public static function fromIterable(iterable $iterable): self */ public static function fromResource($resource): self { - return new self( - /** - * @param resource $resource - */ - static fn ($resource): Iterator => new ResourceIterator($resource), - [$resource] - ); + return new self(static fn (): Iterator => new ResourceIterator($resource)); } /** @@ -539,10 +529,7 @@ public static function fromResource($resource): self */ public static function fromString(string $string, string $delimiter = ''): self { - return new self( - static fn (string $string, string $delimiter): Iterator => new StringIterator($string, $delimiter), - [$string, $delimiter] - ); + return new self(static fn (): Iterator => new StringIterator($string, $delimiter)); } /** diff --git a/src/Iterator/ResourceIterator.php b/src/Iterator/ResourceIterator.php index df0663009..adfc4ec94 100644 --- a/src/Iterator/ResourceIterator.php +++ b/src/Iterator/ResourceIterator.php @@ -39,7 +39,7 @@ public function __construct($resource, bool $closeResource = false) * * @return Generator */ - static function ($resource, bool $closeResource): Generator { + static function ($resource) use ($closeResource): Generator { try { while (false !== $chunk = fgetc($resource)) { yield $chunk; @@ -51,6 +51,6 @@ static function ($resource, bool $closeResource): Generator { } }; - $this->iterator = new ClosureIterator($callback, [$resource, $closeResource]); + $this->iterator = new ClosureIterator($callback, [$resource]); } } diff --git a/src/Iterator/TypedIterator.php b/src/Iterator/TypedIterator.php index 803206cff..da6ae1363 100644 --- a/src/Iterator/TypedIterator.php +++ b/src/Iterator/TypedIterator.php @@ -57,13 +57,16 @@ static function ($variable): string { $this->iterator = new ClosureIterator( /** * @param Iterator $iterator - * @param callable(T): string $getType * * @return Generator */ - static function (Iterator $iterator, callable $getType): Generator { + static function (Iterator $iterator) use ($getType): Generator { $previousType = null; + /** + * @var TKey $key + * @var T $value + */ foreach ($iterator as $key => $value) { if (null === $value) { yield $key => $value; @@ -87,7 +90,7 @@ static function (Iterator $iterator, callable $getType): Generator { yield $key => $value; } }, - [$iterator, $getType] + [$iterator] ); } }