Skip to content

Commit 618271e

Browse files
committed
feat(collections): add immutable list ranges
1 parent 55de92d commit 618271e

7 files changed

Lines changed: 83 additions & 20 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,613 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,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.
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: 15 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 three bounded
7+
repair, three P1 parity repairs, P1 portability revalidation, and four bounded
88
P2 API slices are complete: 41 physical modules, 90 production dependency
9-
edges, and 12,613 tests across 37 executables.*
9+
edges, and 12,616 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,613 passing tests
31+
- The full native baseline is a warning-free build with 12,616 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,13 +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,745 completed tickets. Ticket #1737 records the completed P0 split, tickets
49+
1,746 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
54-
`BinaryReader::PeekChar`, and #1745 `ImmutableList<T>::Sort`/`Reverse`. The
55-
database is git-ignored and is not part of a fresh clone.
54+
`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.
5657

5758
## P0 completion: restore Collections isolation
5859

@@ -139,6 +140,13 @@ leave the source unchanged; range and custom-comparer overloads remain
139140
deferred. Three regressions cover ordering, immutability, and empty/singleton
140141
lists.
141142

143+
## P2 completion: `ImmutableList<T>::GetRange`
144+
145+
`GetRange(index, count)` returns an independently backed, ordered immutable
146+
slice. It accepts zero-length ranges at either valid boundary and reuses the
147+
same `ArgumentOutOfRangeException` checks as `RemoveRange`. Three regressions
148+
cover slice content, source immutability, boundary empties, and invalid ranges.
149+
142150
## Recommended next bounded tasks
143151

144152
All currently planned P1 work is complete. Choose one consumer-driven P2
@@ -148,7 +156,7 @@ slice, create a ticket, and keep the changes isolated:
148156
`Read(char[])` behavior while preserving decoder state and truncated-input
149157
semantics, including supplementary UTF-8 characters.
150158
2. **`ImmutableList<T>` breadth.** Select one real consumer-needed group from
151-
copy/range/conversion, builder support, or comparer overloads.
159+
copying/conversion, builder support, or comparer overloads.
152160
3. **Other documented partial surfaces.** Examples include `BigInteger`
153161
bitwise operations, fuller UTF-7 behavior, and wider debugger/process/XML
154162
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,613 passing tests across 37 test executables**.
10+
with **12,616 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,613 tests. |
184+
| Linux/GCC | Current full component build and all 12,616 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,613 tests across 36
134+
The verified 2026-07-25 `All` baseline contains 12,616 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: 17 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, GetRange, ConvertAll<TOutput>, ToBuilder/Builder,
30+
* CopyTo overloads, ConvertAll<TOutput>, 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
@@ -274,6 +274,22 @@ class ImmutableList {
274274
return ImmutableList<T>(std::move(v));
275275
}
276276

277+
/**
278+
* @brief Returns a new list containing the contiguous range at @p index.
279+
*
280+
* C++ counterpart of .NET ImmutableList<T>.GetRange(int, int).
281+
* @param index The zero-based first element in the range.
282+
* @param count The number of elements to copy into the result.
283+
* @return An immutable list containing the requested range.
284+
* @throws System::ArgumentOutOfRangeException if the range is outside this list.
285+
*/
286+
[[nodiscard]] ImmutableList<T> GetRange(intcs index, intcs count) const {
287+
requireValidRange(index, count);
288+
auto values = std::make_shared<std::vector<T>>(
289+
data_->begin() + index, data_->begin() + index + count);
290+
return ImmutableList<T>(std::move(values));
291+
}
292+
277293
/**
278294
* @brief Returns a new list with all elements sorted using T::operator<.
279295
*
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 "System/ArgumentOutOfRangeException.hpp"
7+
#include "System/Collections/Immutable/ImmutableList.hpp"
8+
9+
using System::Collections::Immutable::ImmutableList;
10+
11+
TEST(ImmutableListRangeTests, GetRangeReturnsOrderedIndependentSlice) {
12+
const auto source = ImmutableList<int>::Create({10, 20, 30, 40, 50});
13+
const auto range = source.GetRange(1, 3);
14+
15+
ASSERT_EQ(range.getCountProperty(), 3);
16+
EXPECT_EQ(range[0], 20);
17+
EXPECT_EQ(range[1], 30);
18+
EXPECT_EQ(range[2], 40);
19+
EXPECT_EQ(source.getCountProperty(), 5);
20+
EXPECT_EQ(source[1], 20);
21+
EXPECT_EQ(source[3], 40);
22+
}
23+
24+
TEST(ImmutableListRangeTests, GetRangeAllowsEmptyRangeAtBothBoundaries) {
25+
const auto source = ImmutableList<int>::Create({10, 20});
26+
27+
EXPECT_TRUE(source.GetRange(0, 0).getIsEmptyProperty());
28+
EXPECT_TRUE(source.GetRange(2, 0).getIsEmptyProperty());
29+
}
30+
31+
TEST(ImmutableListRangeTests, GetRangeInvalidRangesThrowArgumentOutOfRangeException) {
32+
const auto source = ImmutableList<int>::Create({10, 20});
33+
34+
EXPECT_THROW(source.GetRange(-1, 0), System::ArgumentOutOfRangeException);
35+
EXPECT_THROW(source.GetRange(3, 0), System::ArgumentOutOfRangeException);
36+
EXPECT_THROW(source.GetRange(1, -1), System::ArgumentOutOfRangeException);
37+
EXPECT_THROW(source.GetRange(1, 2), System::ArgumentOutOfRangeException);
38+
}

plan.md

Lines changed: 9 additions & 8 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,613 passing tests across 37
4+
dependency edges, a clean native build, 12,616 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,613 passing across 36 component binaries plus one integration
35+
- Tests: 12,616 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,745 rows, all `done`; no `todo`, `doing`, `blocked`, or `needs_user` rows |
55+
| `ticket` | 1,746 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.
@@ -92,6 +92,8 @@ assertion without an explicit architecture decision.
9292
empty-list, ordering, and immutability regression coverage.
9393
- Added default full-list `ImmutableList<T>::Sort` and `Reverse` under ticket
9494
#1745, leaving range and custom-comparer overloads explicitly deferred.
95+
- Added `ImmutableList<T>::GetRange` under ticket #1746, including exact
96+
boundary and invalid-range regression coverage.
9597
- Added seekable `BinaryReader::PeekChar` under ticket #1744. It returns the
9698
next UTF-8 character or EOF without advancing, restores the position after
9799
decode failure, and explicitly rejects non-seekable streams rather than
@@ -156,7 +158,7 @@ The first consumer-driven ports after modularization added:
156158
- XML schema exception types.
157159

158160
The verified test baseline grew from 12,494 at the modularization checkpoint
159-
to 12,613.
161+
to 12,616.
160162

161163
## Candidate roadmap
162164

@@ -166,10 +168,9 @@ acceptance criteria and a validation command before changing code.
166168
### P2 — Consumer-driven API breadth
167169

168170
1. **Continue a bounded `ImmutableList<T>` slice.**
169-
Its documented omissions include copy/range/conversion, builder support,
170-
and comparer overloads. Do not attempt the entire surface in one change;
171-
select methods required by a real consumer and port them against the .NET
172-
reference.
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.
173174

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

0 commit comments

Comments
 (0)