From 4ad971b431da93e62d3b8c89b80ef44e694742cb Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Wed, 28 Oct 2026 10:05:33 +0100 Subject: [PATCH] document enum ordering of native sorts --- README.md | 7 ++++--- src/Collections/transforms.php | 6 ++++-- tests/Collections/TransformsTest.php | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 874a1bc..0aebc75 100644 --- a/README.md +++ b/README.md @@ -268,9 +268,10 @@ A few consequences worth knowing: - `merge()` merges arrays and `Ds\Map` by key (integer keys of arrays are kept, unlike `array_merge()`); for other inputs it appends. - `sort()`/`sortBy()` are stable and never modify their input, unlike `Ds\Seq::sort()`. -- `sort()` without a comparator and `sortBy()` sort natively, comparing like `<=>`. The one difference is enums, - which `<=>` can't order: they are grouped by case and placed after the other values (before them in descending - order). +- `sort()` without a comparator and `sortBy()` sort natively, comparing like `<=>`. The one difference is enums + used directly as the sort key (or as the values for `sort()`), which `<=>` can't order: they are grouped by case, + in an unspecified order (PHP compares object addresses, not declaration order), and placed after the other values + (before them in descending order). Enums nested inside arrays compare as `<=>` does. ### Finding things: `first`, `find` and `required` diff --git a/src/Collections/transforms.php b/src/Collections/transforms.php index 253baa9..8fbaf63 100644 --- a/src/Collections/transforms.php +++ b/src/Collections/transforms.php @@ -310,7 +310,8 @@ function uniqueBy(callable $selector): \Closure /** * Sorts the values with `$comparator($a, $b)` (an int like `<=>`), or with `<=>` when it is null. * - * The sort is stable and keeps the keys of arrays and `Ds\Map`. The input is never modified. + * The sort is stable and keeps the keys of arrays and `Ds\Map`. The input is never modified. Without a comparator, + * enums are ordered differently from `<=>` (see "What you get back" in the README). * * @template TValue = mixed * @@ -372,7 +373,8 @@ function sort(?callable $comparator = null): \Closure * Sorts the values by `$selector($value, $key)`, compared with `<=>`, in ascending or descending order. * * The sort is stable (equal values keep their order in both directions) and keeps the keys of arrays and - * `Ds\Map`. The selector is called once per value. The input is never modified. + * `Ds\Map`. The selector is called once per value. The input is never modified. Enum keys are ordered differently + * from `<=>` (see "What you get back" in the README). * * @template TKey = mixed * @template TValue = mixed diff --git a/tests/Collections/TransformsTest.php b/tests/Collections/TransformsTest.php index dccff5e..e9a10a7 100644 --- a/tests/Collections/TransformsTest.php +++ b/tests/Collections/TransformsTest.php @@ -349,6 +349,21 @@ class TransformsTest extends \PHPUnit\Framework\TestCase $this->assertSame([1, 2], $sorted->values()->toArray()); } + public function testSortWithComparatorKeepsObjectKeysOfMap() + { + $a = new \stdClass(); + $b = new \stdClass(); + $map = new Map(); + $map->put($a, 1); + $map->put($b, 2); + + $sorted = c\sort(fn ($x, $y) => $y <=> $x)($map); + + $this->assertInstanceOf(Map::class, $sorted); + $this->assertSame([$b, $a], $sorted->keys()->toArray()); + $this->assertSame([2, 1], $sorted->values()->toArray()); + } + public function testSortByDoesNotMutateInput() { $array = ['x' => 3, 'y' => 1, 'z' => 2];