diff --git a/docs/pages/api.rst b/docs/pages/api.rst index 0ac03873c..bf1d1afe5 100644 --- a/docs/pages/api.rst +++ b/docs/pages/api.rst @@ -1084,13 +1084,11 @@ Signature: ``Collection::group(): Collection;`` groupBy ~~~~~~~ -Group items based on their keys. - -The default behaviour can be customized with a callback. +Group items based on the provided callback. Interface: `GroupByable`_ -Signature: ``Collection::groupBy(?callable $callback = null): Collection;`` +Signature: ``Collection::groupBy(callable $callback): Collection;`` .. code-block:: php @@ -1104,7 +1102,7 @@ Signature: ``Collection::groupBy(?callable $callback = null): Collection;`` }; $collection = Collection::fromIterable($callback()) - ->groupBy(); // [1 => ['a', 'b', 'c'], 2 => ['d', 'e'], 3 => ['f']] + ->groupBy(static fn (string $char, int $key): int => $key); // [1 => ['a', 'b', 'c'], 2 => ['d', 'e'], 3 => ['f']] has ~~~ diff --git a/spec/loophp/collection/CollectionSpec.php b/spec/loophp/collection/CollectionSpec.php index 99f53e25c..b07cea777 100644 --- a/spec/loophp/collection/CollectionSpec.php +++ b/spec/loophp/collection/CollectionSpec.php @@ -1805,7 +1805,7 @@ public function it_can_groupBy(): void }; $this::fromCallable($callback) - ->groupBy() + ->groupBy(static fn (string $value, int $key): int => $key) ->shouldIterateAs([ 1 => [ 'a', @@ -1854,11 +1854,6 @@ public function it_can_groupBy(): void 19, ], ]); - - $input = range(0, 20); - $this::fromIterable($input) - ->groupBy(static function () {return null; }) - ->shouldIterateAs($input); } public function it_can_has(): void diff --git a/src/Collection.php b/src/Collection.php index c9f69ef3e..d20a42192 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -556,7 +556,7 @@ public function group(): CollectionInterface return new self(Group::of(), [$this->getIterator()]); } - public function groupBy(?callable $callable = null): CollectionInterface + public function groupBy(callable $callable): CollectionInterface { return new self(GroupBy::of()($callable), [$this->getIterator()]); } diff --git a/src/Contract/Operation/GroupByable.php b/src/Contract/Operation/GroupByable.php index 2a3238e21..1dfc705fe 100644 --- a/src/Contract/Operation/GroupByable.php +++ b/src/Contract/Operation/GroupByable.php @@ -23,7 +23,11 @@ interface GroupByable * * @see https://loophp-collection.readthedocs.io/en/stable/pages/api.html#groupby * - * @return Collection + * @template NewTKey of array-key + * + * @param callable(T=, TKey=): NewTKey $callable + * + * @return Collection> */ - public function groupBy(?callable $callable = null): Collection; + public function groupBy(callable $callable): Collection; } diff --git a/src/Operation/GroupBy.php b/src/Operation/GroupBy.php index 24c483d51..4dbf63a86 100644 --- a/src/Operation/GroupBy.php +++ b/src/Operation/GroupBy.php @@ -26,52 +26,40 @@ final class GroupBy extends AbstractOperation /** * @pure * - * @return Closure((null | callable(TKey, T ): (TKey | null))):Closure (Iterator): Generator> + * @template NewTKey of array-key + * + * @return Closure(callable(T=, TKey=): NewTKey):Closure(Iterator): Generator> */ public function __invoke(): Closure { return /** - * @param null|callable(TKey, T):(TKey|null) $callable + * @param callable(T=, TKey=): NewTKey $callable * - * @return Closure(Iterator): Generator> + * @return Closure(Iterator): Generator> */ - static function (?callable $callable = null): Closure { - /** @var callable(T, TKey): (TKey|null) $callable */ - $callable = $callable ?? - /** - * @param T $value - * @param TKey $key - * - * @return TKey - */ - static fn ($value, $key) => $key; - + static function (callable $callable): Closure { $reducerFactory = /** - * @param callable(T, TKey): (TKey|null) $callback + * @param callable(T=, TKey=): NewTKey $callback * - * @return Closure(array>, T, TKey): array> + * @return Closure(array>, T, TKey): array> */ static fn (callable $callback): Closure => /** - * @param array> $collect + * @param array> $collect * @param T $value * @param TKey $key * - * @return non-empty-array> + * @return non-empty-array> */ static function (array $collect, $value, $key) use ($callback): array { - if (null !== $groupKey = $callback($value, $key)) { - $collect[$groupKey][] = $value; - } else { - $collect[$key] = $value; - } + $collect[$callback($value, $key)][] = $value; return $collect; }; - /** @var Closure(Iterator): Generator> $pipe */ + /** @var Closure(Iterator): Generator> $pipe */ $pipe = Pipe::of()( Reduce::of()($reducerFactory($callable))([]), Flatten::of()(1) diff --git a/tests/static-analysis/groupbyable.php b/tests/static-analysis/groupbyable.php new file mode 100644 index 000000000..448846c6a --- /dev/null +++ b/tests/static-analysis/groupbyable.php @@ -0,0 +1,47 @@ +> $collection + */ +function groupby_sameKeyType(CollectionInterface $collection): void +{ +} + +/** + * @param CollectionInterface> $collection + */ +function groupby_newKeyType(CollectionInterface $collection): void +{ +} + +$foo = [ + 0 => 'foo', + 1 => 'bar', + 2 => 'baz', +]; + +groupby_sameKeyType( + Collection::fromIterable($foo)->groupBy(static fn (string $value, int $key): int => $key) +); + +groupby_newKeyType( + Collection::fromIterable($foo)->groupBy(static fn (string $value): string => $value) +); + +// VALID failure -> invalid key types +/** @psalm-suppress InvalidArgument @phpstan-ignore-next-line */ +Collection::fromIterable($foo)->groupBy(static fn () => null); +/** @psalm-suppress InvalidArgument @phpstan-ignore-next-line */ +Collection::fromIterable($foo)->groupBy(static fn (): stdClass => new stdClass());