Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tests/Type/Collection/AggregationTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
35 changes: 33 additions & 2 deletions tests/Type/Collection/TransformTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,19 @@
assertType('Noctud\Collection\Collection<int>', $c->mapNotNull(fn (string $x): ?int => $x !== '' ? 1 : null));
assertType('Noctud\Collection\Collection<int>', $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<string>', $c->flatten());

// Plain arrays are iterables too.
/** @var Collection<array<int>> $arrays */
$arrays = listOf([[1, 2], [3]]);
assertType('Noctud\Collection\Collection<int>', $arrays->flatten());

// ...and the conditional distributes over union element types.
/** @var Collection<array<int>|string> $mixed */
$mixed = listOf([[1], 'a']);
assertType('Noctud\Collection\Collection<int|string>', $mixed->flatten());

// Slicing preserves the element type.
assertType('Noctud\Collection\Collection<string>', $c->takeFirst(2));
assertType('Noctud\Collection\Collection<string>', $c->dropFirst(2));
Expand Down Expand Up @@ -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<string>', $c->intersect(['a', 'b']));
assertType('Noctud\Collection\Set\Set<string>', $c->union(['a', 'b']));
assertType('Noctud\Collection\Set\Set<string>', $c->subtract(['a', 'b']));

/** @var Collection<int|string> $scalars */
$scalars = listOf([1, 'a']);
/** @var iterable<int> $ints */
$ints = [1, 2];
/** @var iterable<mixed> $anything */
$anything = [1, 'a'];

// intersect narrows to E&V: only values that can belong to both sides.
assertType('Noctud\Collection\Set\Set<int>', $scalars->intersect($ints));
assertType('Noctud\Collection\Set\Set<string>', $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<int|string>', $c->union($ints));
assertType('Noctud\Collection\Set\Set<int|string>', $scalars->union($ints));

// subtract always keeps E: any iterable may be subtracted.
assertType('Noctud\Collection\Set\Set<int|string>', $scalars->subtract($ints));
assertType('Noctud\Collection\Set\Set<string>', $c->subtract($anything));
44 changes: 0 additions & 44 deletions tests/Type/FlattenTypeTest.php

This file was deleted.

16 changes: 16 additions & 0 deletions tests/Type/List/NarrowingTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@
assertType('Noctud\Collection\List\ImmutableList<bool>', $imm->map(fn (int $x): bool => $x > 0));
assertType('Noctud\Collection\List\ImmutableList<stdClass>', $imm->filterInstanceOf(stdClass::class));

// flatten() extracts the element type of iterable elements (one level).
assertType('Noctud\Collection\List\ImmutableList<int>', listOf([listOf([1, 2]), listOf([3])])->flatten());

// Only one level is flattened.
assertType(
'Noctud\Collection\List\ImmutableList<Noctud\Collection\List\ImmutableList<int>>',
listOf([listOf([listOf([1])])])->flatten(),
);

// Non-iterable elements are kept as-is.
assertType('Noctud\Collection\List\ImmutableList<string>', 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.
Expand All @@ -51,6 +66,7 @@
assertType('Noctud\Collection\List\ImmutableList<bool>', $mut->map(fn (int $x): bool => $x > 0));
assertType('Noctud\Collection\List\ImmutableList<int>', $mut->sorted());
assertType('Noctud\Collection\List\ImmutableList<int>', mutableListOf([1, null])->filterNotNull());
assertType('Noctud\Collection\List\ImmutableList<int>', mutableListOf([listOf([1, 2])])->flatten());

// The base ListInterface contract keeps everything at the ListInterface level.
/** @var ListInterface<int> $list */
Expand Down
27 changes: 0 additions & 27 deletions tests/Type/MinMaxOfTypeTest.php

This file was deleted.

11 changes: 11 additions & 0 deletions tests/Type/Set/NarrowingTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -39,6 +40,16 @@
assertType('Noctud\Collection\Set\ImmutableSet<bool>', $imm->map(fn (int $x): bool => $x > 0));
assertType('Noctud\Collection\Set\ImmutableSet<stdClass>', $imm->filterInstanceOf(stdClass::class));

// flatten() extracts the element type of iterable elements (one level).
assertType('Noctud\Collection\Set\ImmutableSet<int>', 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<string> $strings */
$strings = ['a', 'b'];
assertType('Noctud\Collection\Set\Set<*NEVER*>', $imm->intersect($strings)); // @phpstan-ignore method.unresolvableReturnType
assertType('Noctud\Collection\Set\Set<int|string>', $imm->union($strings));
assertType('Noctud\Collection\Set\Set<int>', $imm->subtract($strings));

// The base Set contract keeps everything at the Set interface level.
/** @var Set<int> $set */
$set = setOf([1, 2, 3]);
Expand Down
51 changes: 0 additions & 51 deletions tests/Type/SetOperationsTypeTest.php

This file was deleted.

Loading