diff --git a/tests/Type/Collection/AggregationTypeTest.php b/tests/Type/Collection/AggregationTypeTest.php index 20b9c6b..25b2993 100644 --- a/tests/Type/Collection/AggregationTypeTest.php +++ b/tests/Type/Collection/AggregationTypeTest.php @@ -29,6 +29,19 @@ assertType('int', $c->max()); assertType('int|null', $c->maxOrNull()); +// minOf/maxOf return the selector's type R instead of mixed. +$strings = listOf(['a', 'bb']); +assertType('int', $strings->minOf(fn (string $s): int => (int) $s)); +assertType('int', $strings->maxOf(fn (string $s): int => (int) $s)); +assertType('float', $strings->maxOf(fn (string $s): float => (float) $s)); + +// The OrNull variants add null for the empty-collection case. +assertType('int|null', $strings->minOfOrNull(fn (string $s): int => (int) $s)); +assertType('int|null', $strings->maxOfOrNull(fn (string $s): int => (int) $s)); + +// R is inferred even from an untyped selector parameter. +assertType('int', listOf([1, 2, 3])->minOf(fn ($x) => (int) $x)); + // sum: a collection of ints sums to an int. assertType('int', listOf([1, 2, 3])->sum()); diff --git a/tests/Type/Collection/TransformTypeTest.php b/tests/Type/Collection/TransformTypeTest.php index b3b2cd1..474dd90 100644 --- a/tests/Type/Collection/TransformTypeTest.php +++ b/tests/Type/Collection/TransformTypeTest.php @@ -31,9 +31,19 @@ assertType('Noctud\Collection\Collection', $c->mapNotNull(fn (string $x): ?int => $x !== '' ? 1 : null)); assertType('Noctud\Collection\Collection', $c->flatMap(fn (string $x): array => [(int) $x])); -// flatten() keeps non-iterable elements as-is; see FlattenTypeTest for the nesting cases. +// flatten() keeps non-iterable elements as-is. assertType('Noctud\Collection\Collection', $c->flatten()); +// Plain arrays are iterables too. +/** @var Collection> $arrays */ +$arrays = listOf([[1, 2], [3]]); +assertType('Noctud\Collection\Collection', $arrays->flatten()); + +// ...and the conditional distributes over union element types. +/** @var Collection|string> $mixed */ +$mixed = listOf([[1], 'a']); +assertType('Noctud\Collection\Collection', $mixed->flatten()); + // Slicing preserves the element type. assertType('Noctud\Collection\Collection', $c->takeFirst(2)); assertType('Noctud\Collection\Collection', $c->dropFirst(2)); @@ -64,7 +74,28 @@ $c->groupBy(fn (string $x): string => $x, fn (string $x): int => (int) $x), ); -// Set operations. +// Set operations: with a same-typed iterable, the element type is unchanged. assertType('Noctud\Collection\Set\Set', $c->intersect(['a', 'b'])); assertType('Noctud\Collection\Set\Set', $c->union(['a', 'b'])); assertType('Noctud\Collection\Set\Set', $c->subtract(['a', 'b'])); + +/** @var Collection $scalars */ +$scalars = listOf([1, 'a']); +/** @var iterable $ints */ +$ints = [1, 2]; +/** @var iterable $anything */ +$anything = [1, 'a']; + +// intersect narrows to E&V: only values that can belong to both sides. +assertType('Noctud\Collection\Set\Set', $scalars->intersect($ints)); +assertType('Noctud\Collection\Set\Set', $c->intersect($anything)); +// Intersecting disjoint types is reported as an unresolvable return type. +assertType('Noctud\Collection\Set\Set<*NEVER*>', $c->intersect($ints)); // @phpstan-ignore method.unresolvableReturnType + +// union widens to E|V: elements of both sides end up in the result. +assertType('Noctud\Collection\Set\Set', $c->union($ints)); +assertType('Noctud\Collection\Set\Set', $scalars->union($ints)); + +// subtract always keeps E: any iterable may be subtracted. +assertType('Noctud\Collection\Set\Set', $scalars->subtract($ints)); +assertType('Noctud\Collection\Set\Set', $c->subtract($anything)); diff --git a/tests/Type/FlattenTypeTest.php b/tests/Type/FlattenTypeTest.php deleted file mode 100644 index 7efb211..0000000 --- a/tests/Type/FlattenTypeTest.php +++ /dev/null @@ -1,44 +0,0 @@ -', listOf([listOf([1, 2]), listOf([3])])->flatten()); -assertType('Noctud\Collection\Set\ImmutableSet', setOf([listOf([1, 2]), listOf([3])])->flatten()); -assertType('Noctud\Collection\List\ImmutableList', mutableListOf([listOf([1, 2])])->flatten()); - -// Only one level is flattened. -assertType( - 'Noctud\Collection\List\ImmutableList>', - listOf([listOf([listOf([1])])])->flatten(), -); - -// Plain arrays are iterables too. -/** @var Collection> $arrays */ -$arrays = listOf([[1, 2], [3]]); -assertType('Noctud\Collection\Collection', $arrays->flatten()); - -// Non-iterable elements are kept as-is... -assertType('Noctud\Collection\List\ImmutableList', listOf(['a', 'b'])->flatten()); - -// ...and the conditional distributes over union element types. -/** @var Collection|string> $mixed */ -$mixed = listOf([[1], 'a']); -assertType('Noctud\Collection\Collection', $mixed->flatten()); - -// flatten() on an empty collection stays typed (E = never). -assertType('Noctud\Collection\List\ImmutableList<*NEVER*>', listOf([])->flatten()); diff --git a/tests/Type/List/NarrowingTypeTest.php b/tests/Type/List/NarrowingTypeTest.php index 1f5babe..be0b95e 100644 --- a/tests/Type/List/NarrowingTypeTest.php +++ b/tests/Type/List/NarrowingTypeTest.php @@ -40,6 +40,21 @@ assertType('Noctud\Collection\List\ImmutableList', $imm->map(fn (int $x): bool => $x > 0)); assertType('Noctud\Collection\List\ImmutableList', $imm->filterInstanceOf(stdClass::class)); +// flatten() extracts the element type of iterable elements (one level). +assertType('Noctud\Collection\List\ImmutableList', listOf([listOf([1, 2]), listOf([3])])->flatten()); + +// Only one level is flattened. +assertType( + 'Noctud\Collection\List\ImmutableList>', + listOf([listOf([listOf([1])])])->flatten(), +); + +// Non-iterable elements are kept as-is. +assertType('Noctud\Collection\List\ImmutableList', listOf(['a', 'b'])->flatten()); + +// flatten() on an empty collection stays typed (E = never). +assertType('Noctud\Collection\List\ImmutableList<*NEVER*>', listOf([])->flatten()); + // chunked/windowed always produce a list of lists. They return the base ListInterface even // on an ImmutableList: ListInterface's element type is invariant, so narrowing the nested // type would break variance in the shared trait — and ListInterface<...> is already correct. @@ -51,6 +66,7 @@ assertType('Noctud\Collection\List\ImmutableList', $mut->map(fn (int $x): bool => $x > 0)); assertType('Noctud\Collection\List\ImmutableList', $mut->sorted()); assertType('Noctud\Collection\List\ImmutableList', mutableListOf([1, null])->filterNotNull()); +assertType('Noctud\Collection\List\ImmutableList', mutableListOf([listOf([1, 2])])->flatten()); // The base ListInterface contract keeps everything at the ListInterface level. /** @var ListInterface $list */ diff --git a/tests/Type/MinMaxOfTypeTest.php b/tests/Type/MinMaxOfTypeTest.php deleted file mode 100644 index 2df4c62..0000000 --- a/tests/Type/MinMaxOfTypeTest.php +++ /dev/null @@ -1,27 +0,0 @@ -minOf(fn (string $s): int => (int) $s)); -assertType('int', $strings->maxOf(fn (string $s): int => (int) $s)); -assertType('float', $strings->maxOf(fn (string $s): float => (float) $s)); - -// The OrNull variants add null for the empty-collection case. -assertType('int|null', $strings->minOfOrNull(fn (string $s): int => (int) $s)); -assertType('int|null', $strings->maxOfOrNull(fn (string $s): int => (int) $s)); - -// R is inferred even from an untyped selector parameter. -assertType('int', listOf([1, 2, 3])->minOf(fn ($x) => (int) $x)); diff --git a/tests/Type/Set/NarrowingTypeTest.php b/tests/Type/Set/NarrowingTypeTest.php index dfbe0a0..8eb1df9 100644 --- a/tests/Type/Set/NarrowingTypeTest.php +++ b/tests/Type/Set/NarrowingTypeTest.php @@ -11,6 +11,7 @@ use Noctud\Collection\Set\Set; use stdClass; +use function Noctud\Collection\listOf; use function Noctud\Collection\mutableSetOf; use function Noctud\Collection\setOf; use function PHPStan\Testing\assertType; @@ -39,6 +40,16 @@ assertType('Noctud\Collection\Set\ImmutableSet', $imm->map(fn (int $x): bool => $x > 0)); assertType('Noctud\Collection\Set\ImmutableSet', $imm->filterInstanceOf(stdClass::class)); +// flatten() extracts the element type of iterable elements (one level). +assertType('Noctud\Collection\Set\ImmutableSet', setOf([listOf([1, 2]), listOf([3])])->flatten()); + +// Cross-typed set operations follow the same E&V / E|V / E rules as on Collection. +/** @var iterable $strings */ +$strings = ['a', 'b']; +assertType('Noctud\Collection\Set\Set<*NEVER*>', $imm->intersect($strings)); // @phpstan-ignore method.unresolvableReturnType +assertType('Noctud\Collection\Set\Set', $imm->union($strings)); +assertType('Noctud\Collection\Set\Set', $imm->subtract($strings)); + // The base Set contract keeps everything at the Set interface level. /** @var Set $set */ $set = setOf([1, 2, 3]); diff --git a/tests/Type/SetOperationsTypeTest.php b/tests/Type/SetOperationsTypeTest.php deleted file mode 100644 index d98ae18..0000000 --- a/tests/Type/SetOperationsTypeTest.php +++ /dev/null @@ -1,51 +0,0 @@ - $c */ -$c = listOf(['a', 'b', 'c']); -/** @var Collection $scalars */ -$scalars = listOf([1, 'a']); -/** @var iterable $ints */ -$ints = [1, 2]; -/** @var iterable $anything */ -$anything = [1, 'a']; - -// With a same-typed iterable, the element type is unchanged. -assertType('Noctud\Collection\Set\Set', $c->intersect(['a', 'b'])); -assertType('Noctud\Collection\Set\Set', $c->union(['a', 'b'])); -assertType('Noctud\Collection\Set\Set', $c->subtract(['a', 'b'])); - -// intersect narrows to E&V: only values that can belong to both sides. -assertType('Noctud\Collection\Set\Set', $scalars->intersect($ints)); -assertType('Noctud\Collection\Set\Set', $c->intersect($anything)); -// Intersecting disjoint types is reported as an unresolvable return type. -assertType('Noctud\Collection\Set\Set<*NEVER*>', $c->intersect($ints)); // @phpstan-ignore method.unresolvableReturnType - -// union widens to E|V: elements of both sides end up in the result. -assertType('Noctud\Collection\Set\Set', $c->union($ints)); -assertType('Noctud\Collection\Set\Set', $scalars->union($ints)); - -// subtract always keeps E: any iterable may be subtracted. -assertType('Noctud\Collection\Set\Set', $scalars->subtract($ints)); -assertType('Noctud\Collection\Set\Set', $c->subtract($anything)); - -// Same behavior on a Set receiver. -$imm = setOf([1, 2, 3]); -/** @var iterable $strings */ -$strings = ['a', 'b']; -assertType('Noctud\Collection\Set\Set<*NEVER*>', $imm->intersect($strings)); // @phpstan-ignore method.unresolvableReturnType -assertType('Noctud\Collection\Set\Set', $imm->union($strings)); -assertType('Noctud\Collection\Set\Set', $imm->subtract($strings));