From 90471caf4fbc9cbe25093d627100b486863e1c4e Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Tue, 2 Jun 2026 08:51:42 +0200 Subject: [PATCH 1/3] =?UTF-8?q?fix(stdlib):=20error=20instead=20of=20panic?= =?UTF-8?q?king=20on=20reversed=20slice=20ranges=20=F0=9F=94=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- manual/src/snippets/slices.md | 4 ++++ ndc_stdlib/src/index.rs | 5 +++++ .../programs/009_slicing/008_reversed_string_slice.ndc | 4 ++++ .../programs/009_slicing/009_reversed_deque_slice.ndc | 8 ++++++++ .../009_slicing/010_reversed_slice_assignment.ndc | 5 +++++ 5 files changed, 26 insertions(+) create mode 100644 tests/functional/programs/009_slicing/008_reversed_string_slice.ndc create mode 100644 tests/functional/programs/009_slicing/009_reversed_deque_slice.ndc create mode 100644 tests/functional/programs/009_slicing/010_reversed_slice_assignment.ndc diff --git a/manual/src/snippets/slices.md b/manual/src/snippets/slices.md index 88296dce..b4997de9 100644 --- a/manual/src/snippets/slices.md +++ b/manual/src/snippets/slices.md @@ -12,3 +12,7 @@ assert_eq([30, 40, 50, 60], my_list[3..=6]); // Negative indices: Counting from the end of the list assert_eq([80, 90], my_list[-3..-1]); ``` + +A range whose start lands after its end (for example `my_list[6..3]`) is out of +bounds and raises an error rather than returning a reversed or empty slice. This +holds for lists, strings, tuples and deques alike. diff --git a/ndc_stdlib/src/index.rs b/ndc_stdlib/src/index.rs index 88cf0fc6..8dca3857 100644 --- a/ndc_stdlib/src/index.rs +++ b/ndc_stdlib/src/index.rs @@ -230,6 +230,11 @@ fn extract_vm_offset(index_value: &Value, size: usize) -> Result Date: Tue, 2 Jun 2026 09:02:54 +0200 Subject: [PATCH 2/3] =?UTF-8?q?fix(stdlib):=20reject=20inclusive=20reverse?= =?UTF-8?q?d=20ranges=20before=20widening=20end=20=F0=9F=94=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reversed-range check ran after the inclusive +1 adjustment, so an off-by-one inclusive range like `1..=0` survived it and silently returned an empty slice instead of erroring. Move the check before the widening. Co-Authored-By: Claude Opus 4.8 (1M context) --- ndc_stdlib/src/index.rs | 13 ++++++++----- .../009_slicing/011_reversed_inclusive_slice.ndc | 4 ++++ 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 tests/functional/programs/009_slicing/011_reversed_inclusive_slice.ndc diff --git a/ndc_stdlib/src/index.rs b/ndc_stdlib/src/index.rs index 8dca3857..806614b3 100644 --- a/ndc_stdlib/src/index.rs +++ b/ndc_stdlib/src/index.rs @@ -225,16 +225,19 @@ fn extract_vm_offset(index_value: &Value, size: usize) -> Result Date: Tue, 2 Jun 2026 09:11:33 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fix(stdlib):=20detect=20reversed=20slices?= =?UTF-8?q?=20from=20unclamped=20endpoints=20=F0=9F=94=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-widening check compared clamped indices, so a reversed range whose endpoints both sit at or beyond the length (e.g. `6..=5` on a 5-char string) collapsed to equal indices and returned an empty slice instead of erroring. Detect reversal from the unclamped endpoints. Co-Authored-By: Claude Opus 4.8 (1M context) --- ndc_stdlib/src/index.rs | 25 +++++++++++++------ .../012_reversed_slice_beyond_length.ndc | 5 ++++ 2 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 tests/functional/programs/009_slicing/012_reversed_slice_beyond_length.ndc diff --git a/ndc_stdlib/src/index.rs b/ndc_stdlib/src/index.rs index 806614b3..968c4ced 100644 --- a/ndc_stdlib/src/index.rs +++ b/ndc_stdlib/src/index.rs @@ -212,6 +212,14 @@ fn to_forward_index(i: i64, size: usize, for_slice: bool) -> Result i64 { + if i < 0 { size as i64 + i } else { i } +} + enum VmOffset { Element(usize), Range(usize, usize), @@ -223,16 +231,19 @@ fn extract_vm_offset(index_value: &Value, size: usize) -> Result