118 lines
4.5 KiB
PHP
118 lines
4.5 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2026 Kacper Donat
|
|
*
|
|
* @author Kacper "Kadet" Donat <kacper@kadet.net>
|
|
*
|
|
* Full license available in separate LICENSE file
|
|
*/
|
|
|
|
/**
|
|
* PHPStan type tests for the collection functions.
|
|
*
|
|
* This file is never executed: `composer phpstan` analyses it, and every `assertType()` that doesn't match the
|
|
* inferred type is reported as an error. `Ds\Seq` is not generic in PHPStan's view (it comes from the php-ds 2.0
|
|
* polyfill), so `Ds\Seq<T>` is shown as `Ds\Seq&iterable<T>`.
|
|
*/
|
|
|
|
use Kadet\Functional\Collections as c;
|
|
use Kadet\Functional\Collections\Lazy as lazy;
|
|
|
|
use function PHPStan\Testing\assertType;
|
|
|
|
final class Foo
|
|
{
|
|
public function __construct(public int $id, public string $name)
|
|
{
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param \Ds\Seq<int> $ints
|
|
* @param \Closure(int): string $format
|
|
* @param \Ds\Seq<Foo> $foos
|
|
* @param \Ds\Map<string, Foo> $map
|
|
* @param \Ds\Set<Foo> $set
|
|
* @param list<Foo> $list
|
|
* @param \Generator<int, Foo> $generator
|
|
*/
|
|
function transforms(
|
|
\Ds\Seq $ints,
|
|
\Closure $format,
|
|
\Ds\Seq $foos,
|
|
\Ds\Map $map,
|
|
\Ds\Set $set,
|
|
array $list,
|
|
\Generator $generator,
|
|
): void {
|
|
// The container is preserved, the element type comes from the callback.
|
|
assertType('Ds\Seq&iterable<string>', c\map($format)($ints));
|
|
assertType('Ds\Seq&iterable<string>', c\map(fn (int $v): string => $format($v))($ints));
|
|
assertType('Ds\Seq&iterable<Foo>', c\filter(fn (Foo $foo): bool => $foo->id > 0)($foos));
|
|
assertType('Ds\Map<string, string>', c\map(fn (Foo $foo, string $key): string => $key)($map));
|
|
assertType('Ds\Set<int>', c\map(fn (Foo $foo): int => $foo->id)($set));
|
|
assertType('array<int|string, Foo>', c\reject(fn (Foo $foo): bool => $foo->id > 0)($list));
|
|
assertType('Ds\Seq&iterable<Foo>', c\sortBy(fn (Foo $foo): string => $foo->name)($foos));
|
|
|
|
// A generator can't be returned half-consumed, so eager transforms give a Seq (or a list without php-ds).
|
|
assertType('(Ds\Seq&iterable<int>)|list<int>', c\map(fn (Foo $foo): int => $foo->id)($generator));
|
|
assertType('(Ds\Seq&iterable<Foo>)|list<Foo>', c\tap(fn (Foo $foo) => null)($generator));
|
|
|
|
// List transforms: a Map gives a Seq.
|
|
assertType('Ds\Seq&iterable<string>', c\flatMap(fn (Foo $foo): array => [$foo->name])($map));
|
|
assertType('list<string>', c\flatMap(fn (Foo $foo): array => [$foo->name])($list));
|
|
|
|
// Transforms without a callback keep the element types, but not the container.
|
|
assertType('iterable<int, Foo>', c\values()($foos));
|
|
assertType('iterable<int<0, max>, Foo>', c\take(2)($list));
|
|
|
|
// Lazy transforms give generators.
|
|
assertType('Generator<mixed, string, mixed, mixed>', lazy\map(fn (Foo $foo): string => $foo->name)($list));
|
|
assertType('Generator<mixed, Foo, mixed, mixed>', lazy\filter(fn (Foo $foo): bool => $foo->id > 0)($list));
|
|
}
|
|
|
|
/**
|
|
* @param \Ds\Seq<Foo> $foos
|
|
* @param list<Foo> $list
|
|
*/
|
|
function collectors(\Ds\Seq $foos, array $list): void
|
|
{
|
|
assertType('Ds\Map<int, Foo>', c\keyBy(fn (Foo $foo): int => $foo->id)($foos));
|
|
assertType('Ds\Map<string, Ds\Seq&iterable<Foo>>', c\groupBy(fn (Foo $foo): string => $foo->name)($list));
|
|
assertType('Ds\Set<Foo>', c\toSet()($list));
|
|
assertType('array<int<0, max>, Foo>', c\toArray()($list));
|
|
}
|
|
|
|
/**
|
|
* @param \Ds\Seq<Foo> $foos
|
|
* @param \Ds\Map<string, Foo> $map
|
|
* @param list<int> $ints
|
|
*/
|
|
function queries(\Ds\Seq $foos, \Ds\Map $map, array $ints): void
|
|
{
|
|
assertType('Foo|null', c\first()($foos));
|
|
assertType('Foo|null', c\first(fn (Foo $foo): bool => $foo->id > 1)($foos));
|
|
assertType('Foo|null', c\last()($foos));
|
|
assertType('Foo|null', c\find(fn (Foo $foo): bool => $foo->id > 1)($map));
|
|
assertType('int|null', c\min()($ints));
|
|
assertType('string|null', c\max(fn (Foo $foo): string => $foo->name)($foos));
|
|
assertType('bool', c\any(fn (Foo $foo): bool => $foo->id > 1)($foos));
|
|
assertType('int', c\count()($foos));
|
|
assertType('int', c\reduce(fn (int $carry, Foo $foo): int => $carry + $foo->id, 0)($foos));
|
|
}
|
|
|
|
/**
|
|
* The pipe operator: PHPStan infers through `|>` like through a call.
|
|
*
|
|
* @param \Ds\Seq<Foo> $foos
|
|
*/
|
|
function pipes(\Ds\Seq $foos): void
|
|
{
|
|
assertType('Ds\Seq&iterable<string>', $foos |> c\map(fn (Foo $foo): string => $foo->name));
|
|
assertType(
|
|
'Foo|null',
|
|
$foos |> c\filter(fn (Foo $foo): bool => $foo->id > 1) |> c\first(),
|
|
);
|
|
assertType('Ds\Map<int, Foo>', $foos |> lazy\filter(fn (Foo $foo): bool => $foo->id > 1) |> c\keyBy(fn (Foo $foo): int => $foo->id));
|
|
}
|