Skip to content

Commit 79b08bf

Browse files
committed
feat(collections): add immutable list comparison sort
1 parent 1ef2a08 commit 79b08bf

7 files changed

Lines changed: 82 additions & 22 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
## Non-negotiable rules
1010

1111
1. **Zero errors, zero warnings** before any commit. `cmake --build build --parallel 4` must be clean.
12-
2. **No test-count regression.** `scripts/run_component_tests.sh build` must show no failures. The verified baseline is 12,630 tests across 36 component executables and one integration executable; this floor should be raised as new tests are added and lowered only with an explicit, documented reason.
12+
2. **No test-count regression.** `scripts/run_component_tests.sh build` must show no failures. The verified baseline is 12,632 tests across 36 component executables and one integration executable; this floor should be raised as new tests are added and lowered only with an explicit, documented reason.
1313
3. **Push only to `feature/work`.** Never push to `develop` or `master`, and never create tags, without explicit per-action user approval.
1414
4. **SPDX header on every project source/header**`// SPDX-License-Identifier: MIT` + copyright + .NET attribution. Vendored sources retain their upstream headers; Markdown uses an HTML SPDX comment where one is present.
1515
5. **Property naming:** always `getXxxProperty()` / `setXxxProperty()`. Exception: indexers

NEXT.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
# NEXT.md
55

66
*Last verified: 2026-07-25. Branch: `feature/work`. The P0 component-boundary
7-
repair, three P1 parity repairs, P1 portability revalidation, and seven bounded
7+
repair, three P1 parity repairs, P1 portability revalidation, and eight bounded
88
P2 API slices are complete: 41 physical modules, 90 production dependency
9-
edges, and 12,630 tests across 37 executables.*
9+
edges, and 12,632 tests across 37 executables.*
1010

1111
This is the cold-start handoff for the next working session. Keep it focused
1212
on verified facts, remaining bounded work, and commands needed to resume.
@@ -28,7 +28,7 @@ Historical session detail belongs in git history and `plan.sqlite3`.
2828
- The ten-job selective consumer matrix, including a direct
2929
`Collections.Blocking` consumer, is green. Text.Json retains its target
3030
absence and negative include-leakage assertions.
31-
- The full native baseline is a warning-free build with 12,630 passing tests
31+
- The full native baseline is a warning-free build with 12,632 passing tests
3232
across 36 component executables and one integration executable.
3333
- `TaskT<TResult>::ContinueWith` now supports both action and result-producing
3434
callbacks. It runs inline on completion; `NotOn*` and `OnlyOn*` filter the
@@ -43,6 +43,9 @@ Historical session detail belongs in git history and `plan.sqlite3`.
4343
- `ImmutableList<T>` supports all three `CopyTo` overloads using a fixed-size
4444
`std::vector` destination. Its bounds checks distinguish invalid source
4545
ranges from an undersized destination and avoid signed-overflow-prone sums.
46+
- `ImmutableList<T>::Sort(Comparison<T>)` returns an independently backed
47+
custom-ordered result using the established signed comparison delegate and
48+
rejects an empty delegate.
4649
- MinGW-w64 GCC 14-win32/CMake 3.31.6 and Emscripten 5.0.7/CMake 3.31.6 both
4750
compile the post-modular `All` graph and selective `Text.Json` libraries.
4851
This is compile-only evidence: cross tests were deliberately disabled.
@@ -51,15 +54,16 @@ Historical session detail belongs in git history and `plan.sqlite3`.
5154
ASan/LSan ownership scenarios, including 100 continuation teardowns, pass.
5255

5356
The local `plan.sqlite3` snapshot contains 16,201 classified `task` rows and
54-
1,749 completed tickets. Ticket #1737 records the completed P0 split, tickets
57+
1,750 completed tickets. Ticket #1737 records the completed P0 split, tickets
5558
#1738/#1739 the MemoryStream and generic-continuation repairs, ticket #1740 the
5659
XML whitespace repair, #1741 the completed cross-build revalidation and
5760
`WebProxy` portability fix, #1742 focused sanitizer evidence, and #1743 the
5861
`ImmutableList<T>` predicate-query slice. Ticket #1744 records seekable
5962
`BinaryReader::PeekChar`, #1745 `ImmutableList<T>::Sort`/`Reverse`, and #1746
6063
`ImmutableList<T>::GetRange`, #1747 `ImmutableList<T>::ConvertAll`, #1748 the
61-
UTF-8 `BinaryReader` batch-character APIs, and #1749 `ImmutableList<T>`
62-
copying. The database is git-ignored and is not part of a fresh clone.
64+
UTF-8 `BinaryReader` batch-character APIs, #1749 `ImmutableList<T>` copying,
65+
and #1750 its custom comparison sort. The database is git-ignored and is not
66+
part of a fresh clone.
6367

6468
## P0 completion: restore Collections isolation
6569

@@ -176,13 +180,20 @@ They preserve order and source immutability, allow valid empty end ranges,
176180
and distinguish invalid indices/ranges from an undersized destination. Five
177181
regressions cover each overload, boundary behavior, and validation.
178182

183+
## P2 completion: `ImmutableList<T>::Sort(Comparison<T>)`
184+
185+
`Sort` now accepts the project's signed `Comparison<T>` delegate convention:
186+
negative is before, zero equivalent, positive after. It returns an
187+
independently backed result and rejects an empty delegate with
188+
`ArgumentNullException`; two regressions cover custom ordering and validation.
189+
179190
## Recommended next bounded tasks
180191

181192
All currently planned P1 work is complete. Choose one consumer-driven P2
182193
slice, create a ticket, and keep the changes isolated:
183194

184195
1. **`ImmutableList<T>` breadth.** Select one real consumer-needed group from
185-
builder support or comparer overloads.
196+
builder support, range operations, or remaining comparer overloads.
186197
2. **Other documented partial surfaces.** Examples include `BigInteger`
187198
bitwise operations, fuller UTF-7 behavior, and wider debugger/process/XML
188199
surfaces.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ collector, or the complete .NET platform.
77

88
The repository currently builds as 41 independently selectable CMake
99
components. The verified Linux baseline on 2026-07-25 is a warning-free build
10-
with **12,630 passing tests across 37 test executables**.
10+
with **12,632 passing tests across 37 test executables**.
1111

1212
## What is included
1313

@@ -181,7 +181,7 @@ Other platform evidence is narrower:
181181

182182
| Platform/toolchain | Verified scope |
183183
|---|---|
184-
| Linux/GCC | Current full component build and all 12,630 tests. |
184+
| Linux/GCC | Current full component build and all 12,632 tests. |
185185
| Windows/MinGW | MinGW-w64 GCC 14-win32/CMake 3.31.6 compiled the post-component `All` and selective `Text.Json` library graphs under ticket #1741. GoogleTest was not cross-built and repository CI remains Ubuntu-only. |
186186
| Emscripten | Emscripten 5.0.7/CMake 3.31.6 compiled the post-component `All` and selective `Text.Json` library graphs under ticket #1741. Tests were not cross-built or run, and some runtime APIs deliberately throw `PlatformNotSupportedException`. |
187187
| macOS/Apple Clang | Real downstream Xcode 15.4 builds drove portability fixes on 2026-07-20; this repository has no macOS job or recorded full standalone test baseline. |

docs/CMakeComponents.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ are named `SharpRuntimeTests_<Component>`; genuinely cross-module scenarios
131131
are in `SharpRuntimeIntegrationTests`. CTest also discovers every individual
132132
GoogleTest case.
133133

134-
The verified 2026-07-25 `All` baseline contains 12,630 tests across 36
134+
The verified 2026-07-25 `All` baseline contains 12,632 tests across 36
135135
component executables and one integration executable.
136136

137137
## Boundary validation and CI

modules/collections/include/System/Collections/Immutable/ImmutableList.hpp

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ using SharpRuntime::intcs;
2626
* Remove/RemoveAll/RemoveAt/RemoveRange(int,int)/Sort/Reverse/Contains/IndexOf/LastIndexOf/
2727
* BinarySearch).
2828
* Deliberately deferred relative to real .NET's ImmutableList<T> (a much larger surface backed
29-
* by an AVL tree, not a flat vector): range and custom-comparer Sort/Reverse overloads,
30-
* ToBuilder/Builder,
29+
* by an AVL tree, not a flat vector): range Sort/Reverse overloads, ToBuilder/Builder,
3130
* RemoveRange(IEnumerable<T>), and every
3231
* IEqualityComparer<T>/IComparer<T>-taking overload of Remove/RemoveRange/Replace/IndexOf/
3332
* LastIndexOf/BinarySearch (this port always uses T::operator== / operator< instead). These are
@@ -373,8 +372,8 @@ class ImmutableList {
373372
/**
374373
* @brief Returns a new list with all elements sorted using T::operator<.
375374
*
376-
* C++ counterpart of .NET ImmutableList<T>.Sort(). Range and custom-comparer
377-
* overloads remain deliberately unimplemented.
375+
* C++ counterpart of .NET ImmutableList<T>.Sort(). Range overloads remain
376+
* deliberately unimplemented.
378377
* @return A sorted immutable list; the source list is unchanged.
379378
*/
380379
[[nodiscard]] ImmutableList<T> Sort() const {
@@ -383,6 +382,29 @@ class ImmutableList {
383382
return ImmutableList<T>(std::move(values));
384383
}
385384

385+
/**
386+
* @brief Returns a new list sorted by a custom comparison delegate.
387+
*
388+
* C++ counterpart of .NET ImmutableList<T>.Sort(Comparison<T>). The comparison follows
389+
* the established project convention: negative means before, zero equivalent, and positive
390+
* means after.
391+
* @param comparison The comparison delegate.
392+
* @return A sorted immutable list; the source list is unchanged.
393+
* @throws System::ArgumentNullException if @p comparison is empty.
394+
*/
395+
[[nodiscard]] ImmutableList<T> Sort(std::function<intcs(const T&, const T&)> comparison) const {
396+
if (!comparison) {
397+
throw System::ArgumentNullException("comparison");
398+
}
399+
400+
auto values = std::make_shared<std::vector<T>>(*data_);
401+
std::sort(values->begin(), values->end(),
402+
[&comparison](const T& left, const T& right) {
403+
return comparison(left, right) < 0;
404+
});
405+
return ImmutableList<T>(std::move(values));
406+
}
407+
386408
/**
387409
* @brief Returns a new list whose elements are in reverse order.
388410
*

modules/collections/tests/System/Collections/ImmutableListOrderingTests.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// Portions based on .NET runtime API (MIT License, Copyright .NET Foundation and Contributors)
44
#include <gtest/gtest.h>
55

6+
#include <functional>
7+
8+
#include "System/ArgumentNullException.hpp"
69
#include "System/Collections/Immutable/ImmutableList.hpp"
710

811
using System::Collections::Immutable::ImmutableList;
@@ -44,3 +47,24 @@ TEST(ImmutableListOrderingTests, SortAndReverseHandleEmptyAndSingletonLists) {
4447
ASSERT_EQ(single.Reverse().getCountProperty(), 1);
4548
EXPECT_EQ(single.Reverse()[0], 42);
4649
}
50+
51+
TEST(ImmutableListOrderingTests, SortWithCustomComparisonReturnsIndependentOrder) {
52+
const auto source = ImmutableList<int>::Create({4, 1, 3, 2});
53+
const auto sorted = source.Sort([](const int& left, const int& right) {
54+
return right - left;
55+
});
56+
57+
EXPECT_EQ(source[0], 4);
58+
EXPECT_EQ(source[1], 1);
59+
EXPECT_EQ(sorted[0], 4);
60+
EXPECT_EQ(sorted[1], 3);
61+
EXPECT_EQ(sorted[2], 2);
62+
EXPECT_EQ(sorted[3], 1);
63+
}
64+
65+
TEST(ImmutableListOrderingTests, SortWithEmptyComparisonThrowsArgumentNullException) {
66+
const auto source = ImmutableList<int>::Create({1});
67+
const std::function<SharpRuntime::intcs(const int&, const int&)> comparison;
68+
69+
EXPECT_THROW(source.Sort(comparison), System::ArgumentNullException);
70+
}

plan.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Sharp Runtime plan
22

33
*Last verified: 2026-07-25 — 41 physical components, 90 direct production
4-
dependency edges, a clean native build, 12,630 passing tests across 37
4+
dependency edges, a clean native build, 12,632 passing tests across 37
55
executables, and a green ten-job selective matrix.*
66

77
Sharp Runtime is in a consumer-driven expansion phase. The original type
@@ -32,7 +32,7 @@ was never created. Neither file should be linked as current documentation.
3232
### Code and validation
3333

3434
- Native Linux/GCC build: zero errors and zero warnings.
35-
- Tests: 12,630 passing across 36 component binaries plus one integration
35+
- Tests: 12,632 passing across 36 component binaries plus one integration
3636
binary.
3737
- Component graph: 41 physical modules and 90 direct production edges.
3838
- Boundary validator: no cycles, duplicate public include paths, orphan
@@ -52,7 +52,7 @@ The 2026-07-25 local snapshot contains:
5252
| Table | State |
5353
|---|---|
5454
| `task` | 16,201 rows: 1,082 `ported`, 140 `ignore`, 14,979 legacy `ignored`; no unclassified or `tobedecided` rows |
55-
| `ticket` | 1,749 rows, all `done`; no `todo`, `doing`, `blocked`, or `needs_user` rows |
55+
| `ticket` | 1,750 rows, all `done`; no `todo`, `doing`, `blocked`, or `needs_user` rows |
5656

5757
Because `plan.sqlite3` is git-ignored, these counts describe the maintainer
5858
snapshot, not data shipped in a fresh clone.
@@ -107,6 +107,9 @@ assertion without an explicit architecture decision.
107107
- Added all three `ImmutableList<T>::CopyTo` overloads under ticket #1749,
108108
using checked fixed-size `std::vector` destinations, including source range
109109
and destination offset handling without signed-overflow-prone bounds checks.
110+
- Added `ImmutableList<T>::Sort(Comparison<T>)` under ticket #1750. It follows
111+
the established signed comparison-delegate convention, rejects an empty
112+
delegate, and returns an independently backed sorted list.
110113
- Added consumer-driven coverage across core, collections, IO, networking,
111114
threading/tasks, text/JSON, XML, numerics, globalization, and cryptographic
112115
hashing/random APIs.
@@ -167,7 +170,7 @@ The first consumer-driven ports after modularization added:
167170
- XML schema exception types.
168171

169172
The verified test baseline grew from 12,494 at the modularization checkpoint
170-
to 12,630.
173+
to 12,632.
171174

172175
## Candidate roadmap
173176

@@ -177,9 +180,9 @@ acceptance criteria and a validation command before changing code.
177180
### P2 — Consumer-driven API breadth
178181

179182
1. **Continue a bounded `ImmutableList<T>` slice.**
180-
Its documented omissions include builder support and comparer overloads.
181-
Do not attempt the entire surface in one change; select methods required by
182-
a real consumer and port them against the .NET reference.
183+
Its documented omissions include builder support plus range and remaining
184+
comparer overloads. Do not attempt the entire surface in one change; select
185+
methods required by a real consumer and port them against the .NET reference.
183186

184187
2. **Extend `BinaryReader` only from a concrete consumer need.**
185188
`ReadChar`, `ReadChars`, `Read(char[])`, `ReadDecimal`, and seekable

0 commit comments

Comments
 (0)