Skip to content

Commit ef1ef06

Browse files
committed
feat(collections): add immutable list conversion
1 parent 618271e commit ef1ef06

7 files changed

Lines changed: 92 additions & 19 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,616 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,619 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: 13 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 four bounded
7+
repair, three P1 parity repairs, P1 portability revalidation, and five bounded
88
P2 API slices are complete: 41 physical modules, 90 production dependency
9-
edges, and 12,616 tests across 37 executables.*
9+
edges, and 12,619 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,616 passing tests
31+
- The full native baseline is a warning-free build with 12,619 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
@@ -46,14 +46,14 @@ Historical session detail belongs in git history and `plan.sqlite3`.
4646
ASan/LSan ownership scenarios, including 100 continuation teardowns, pass.
4747

4848
The local `plan.sqlite3` snapshot contains 16,201 classified `task` rows and
49-
1,746 completed tickets. Ticket #1737 records the completed P0 split, tickets
49+
1,747 completed tickets. Ticket #1737 records the completed P0 split, tickets
5050
#1738/#1739 the MemoryStream and generic-continuation repairs, ticket #1740 the
5151
XML whitespace repair, #1741 the completed cross-build revalidation and
5252
`WebProxy` portability fix, #1742 focused sanitizer evidence, and #1743 the
5353
`ImmutableList<T>` predicate-query slice. Ticket #1744 records seekable
5454
`BinaryReader::PeekChar`, #1745 `ImmutableList<T>::Sort`/`Reverse`, and #1746
55-
`ImmutableList<T>::GetRange`. The database is git-ignored and is not part of a
56-
fresh clone.
55+
`ImmutableList<T>::GetRange`, and #1747 `ImmutableList<T>::ConvertAll`. The
56+
database is git-ignored and is not part of a fresh clone.
5757

5858
## P0 completion: restore Collections isolation
5959

@@ -147,6 +147,12 @@ slice. It accepts zero-length ranges at either valid boundary and reuses the
147147
same `ArgumentOutOfRangeException` checks as `RemoveRange`. Three regressions
148148
cover slice content, source immutability, boundary empties, and invalid ranges.
149149

150+
## P2 completion: `ImmutableList<T>::ConvertAll`
151+
152+
`ConvertAll<TOutput>` converts each source value in order into an independently
153+
backed immutable list. It preserves an empty source and rejects an empty
154+
converter with `ArgumentNullException`; three regressions cover those cases.
155+
150156
## Recommended next bounded tasks
151157

152158
All currently planned P1 work is complete. Choose one consumer-driven P2
@@ -156,7 +162,7 @@ slice, create a ticket, and keep the changes isolated:
156162
`Read(char[])` behavior while preserving decoder state and truncated-input
157163
semantics, including supplementary UTF-8 characters.
158164
2. **`ImmutableList<T>` breadth.** Select one real consumer-needed group from
159-
copying/conversion, builder support, or comparer overloads.
165+
copying, builder support, or comparer overloads.
160166
3. **Other documented partial surfaces.** Examples include `BigInteger`
161167
bitwise operations, fuller UTF-7 behavior, and wider debugger/process/XML
162168
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,616 passing tests across 37 test executables**.
10+
with **12,619 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,616 tests. |
184+
| Linux/GCC | Current full component build and all 12,619 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,616 tests across 36
134+
The verified 2026-07-25 `All` baseline contains 12,619 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: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ using SharpRuntime::intcs;
2727
* BinarySearch).
2828
* Deliberately deferred relative to real .NET's ImmutableList<T> (a much larger surface backed
2929
* by an AVL tree, not a flat vector): range and custom-comparer Sort/Reverse overloads, the 3
30-
* CopyTo overloads, ConvertAll<TOutput>, ToBuilder/Builder,
30+
* CopyTo overloads, ToBuilder/Builder,
3131
* RemoveRange(IEnumerable<T>), and every
3232
* IEqualityComparer<T>/IComparer<T>-taking overload of Remove/RemoveRange/Replace/IndexOf/
3333
* LastIndexOf/BinarySearch (this port always uses T::operator== / operator< instead). These are
@@ -290,6 +290,30 @@ class ImmutableList {
290290
return ImmutableList<T>(std::move(values));
291291
}
292292

293+
/**
294+
* @brief Converts every element and returns an immutable list of the converted values.
295+
*
296+
* C++ counterpart of .NET ImmutableList<T>.ConvertAll<TOutput>(Converter<T, TOutput>).
297+
* @tparam TOutput The target element type.
298+
* @param converter The conversion function.
299+
* @return A new immutable list containing converted values in source order.
300+
* @throws System::ArgumentNullException if @p converter is empty.
301+
*/
302+
template<typename TOutput>
303+
[[nodiscard]] ImmutableList<TOutput> ConvertAll(
304+
std::function<TOutput(const T&)> converter) const {
305+
if (!converter) {
306+
throw System::ArgumentNullException("converter");
307+
}
308+
309+
std::vector<TOutput> values;
310+
values.reserve(data_->size());
311+
for (const auto& item : *data_) {
312+
values.push_back(converter(item));
313+
}
314+
return ImmutableList<TOutput>::Create(values);
315+
}
316+
293317
/**
294318
* @brief Returns a new list with all elements sorted using T::operator<.
295319
*
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) Robert Vokac and contributors
3+
// Portions based on .NET runtime API (MIT License, Copyright .NET Foundation and Contributors)
4+
#include <gtest/gtest.h>
5+
6+
#include <functional>
7+
#include <string>
8+
9+
#include "System/ArgumentNullException.hpp"
10+
#include "System/Collections/Immutable/ImmutableList.hpp"
11+
12+
using System::Collections::Immutable::ImmutableList;
13+
14+
TEST(ImmutableListConversionTests, ConvertAllReturnsOrderedConvertedCopy) {
15+
const auto source = ImmutableList<int>::Create({4, 1, 9});
16+
const auto converted = source.ConvertAll<std::string>(
17+
[](const int& value) { return "value=" + std::to_string(value); });
18+
19+
ASSERT_EQ(converted.getCountProperty(), 3);
20+
EXPECT_EQ(converted[0], "value=4");
21+
EXPECT_EQ(converted[1], "value=1");
22+
EXPECT_EQ(converted[2], "value=9");
23+
EXPECT_EQ(source.getCountProperty(), 3);
24+
EXPECT_EQ(source[0], 4);
25+
EXPECT_EQ(source[2], 9);
26+
}
27+
28+
TEST(ImmutableListConversionTests, ConvertAllHandlesEmptySource) {
29+
const auto source = ImmutableList<int>::Empty();
30+
const auto converted = source.ConvertAll<std::string>(
31+
[](const int& value) { return std::to_string(value); });
32+
33+
EXPECT_TRUE(converted.getIsEmptyProperty());
34+
}
35+
36+
TEST(ImmutableListConversionTests, ConvertAllEmptyConverterThrowsArgumentNullException) {
37+
const auto source = ImmutableList<int>::Create({1});
38+
const std::function<std::string(const int&)> converter;
39+
40+
EXPECT_THROW(source.ConvertAll<std::string>(converter), System::ArgumentNullException);
41+
}

plan.md

Lines changed: 9 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,616 passing tests across 37
4+
dependency edges, a clean native build, 12,619 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,616 passing across 36 component binaries plus one integration
35+
- Tests: 12,619 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,746 rows, all `done`; no `todo`, `doing`, `blocked`, or `needs_user` rows |
55+
| `ticket` | 1,747 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.
@@ -94,6 +94,8 @@ assertion without an explicit architecture decision.
9494
#1745, leaving range and custom-comparer overloads explicitly deferred.
9595
- Added `ImmutableList<T>::GetRange` under ticket #1746, including exact
9696
boundary and invalid-range regression coverage.
97+
- Added `ImmutableList<T>::ConvertAll<TOutput>` under ticket #1747, including
98+
empty-source and empty-converter regression coverage.
9799
- Added seekable `BinaryReader::PeekChar` under ticket #1744. It returns the
98100
next UTF-8 character or EOF without advancing, restores the position after
99101
decode failure, and explicitly rejects non-seekable streams rather than
@@ -158,7 +160,7 @@ The first consumer-driven ports after modularization added:
158160
- XML schema exception types.
159161

160162
The verified test baseline grew from 12,494 at the modularization checkpoint
161-
to 12,616.
163+
to 12,619.
162164

163165
## Candidate roadmap
164166

@@ -168,9 +170,9 @@ acceptance criteria and a validation command before changing code.
168170
### P2 — Consumer-driven API breadth
169171

170172
1. **Continue a bounded `ImmutableList<T>` slice.**
171-
Its documented omissions include copying/conversion, builder support, and
172-
comparer overloads. Do not attempt the entire surface in one change; select
173-
methods required by a real consumer and port them against the .NET reference.
173+
Its documented omissions include copying, builder support, and comparer
174+
overloads. Do not attempt the entire surface in one change; select methods
175+
required by a real consumer and port them against the .NET reference.
174176

175177
2. **Complete only demanded `BinaryReader` character APIs.**
176178
`ReadChar`, `ReadDecimal`, and seekable `PeekChar` are implemented, while

0 commit comments

Comments
 (0)