Skip to content

Commit b6a28dc

Browse files
committed
Support negative base arrays
1 parent 06676cc commit b6a28dc

File tree

2 files changed

+71
-4
lines changed

2 files changed

+71
-4
lines changed

Src/IronPython/Runtime/Operations/ArrayOps.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,12 @@ private static int[] TupleToIndices(Array a, IList<object?> tuple) {
437437

438438
private static int FixIndex(Array a, int v, int axis) {
439439
int idx = v;
440-
if (idx < 0) idx += a.GetUpperBound(axis) + 1;
441-
if (idx < a.GetLowerBound(axis) || idx > a.GetUpperBound(axis)) {
440+
int lb = a.GetLowerBound(axis);
441+
int ub = a.GetUpperBound(axis);
442+
if (idx < 0 && lb >= 0) {
443+
idx += ub + 1;
444+
}
445+
if (idx < lb || idx > ub) {
442446
throw PythonOps.IndexError("index out of range: {0}", v);
443447
}
444448
return idx;
@@ -464,7 +468,7 @@ private static void FixSlice(Slice slice, Array a, out int ostart, out int ostop
464468
} else {
465469
ostart = Converter.ConvertToIndex(slice.start);
466470
if (ostart < lb) {
467-
if (ostart < 0) {
471+
if (ostart < 0 && lb >= 0) {
468472
ostart += ub + 1;
469473
}
470474
if (ostart < lb) {
@@ -480,7 +484,7 @@ private static void FixSlice(Slice slice, Array a, out int ostart, out int ostop
480484
} else {
481485
ostop = Converter.ConvertToIndex(slice.stop);
482486
if (ostop < lb) {
483-
if (ostop < 0) {
487+
if (ostop < 0 && lb >= 0) {
484488
ostop += ub + 1;
485489
}
486490
if (ostop < 0) {

Tests/test_array.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@
66
## Test array support by IronPython (System.Array)
77
##
88

9+
"""
10+
Indexing of CLI arrays in IronPython:
11+
12+
13+
| Base | Index >= 0 | Index < 0 |
14+
|------|--------------------------------------|-------------------|
15+
| > 0 | absolue | relative from end |
16+
| 0 | absolute == relative from beginning | relative from end |
17+
| < 0 | absolute | absolute |
18+
19+
Comparison to indexing in C# and CPython:
20+
21+
* Index >= 0, any base is C# compliant.
22+
* Base 0, any index is CPython compliant.
23+
* Base 0, index < 0 is not supported by C# but can be achieved by `System.Index` with 1-dim arrays only; then IronPython indexing is C# compliant.
24+
* Base > 0, index < 0 is not supported by C#; IronPython follows CPython convention as more practical.
25+
* Base < 0, index < 0 is C# compliant.
26+
* Base != 0 is not supported by CPython for any builtin structures.
27+
"""
28+
929
from iptest import IronPythonTestCase, is_cli, run_test, skipUnlessIronPython
1030

1131
if is_cli:
@@ -146,6 +166,13 @@ def test_slice(self):
146166
def f(): array1[::2] = [x * 2 for x in range(11)]
147167
self.assertRaises(ValueError, f)
148168

169+
# slices on non-1-dim arrays are not supported
170+
array2 = System.Array.CreateInstance(int, 20, 20)
171+
self.assertRaises(NotImplementedError, lambda: array2[:]) # TODO: TypeError?
172+
self.assertRaises(TypeError, lambda: array2[:, :]) # TODO: NotImplementedError? This would work in Numpy and Sympy
173+
self.assertRaises(TypeError, lambda: array2[:, :, :]) # OK
174+
175+
149176
def test_creation(self):
150177
t = System.Array
151178
ti = type(System.Array.CreateInstance(int, 1))
@@ -239,6 +266,10 @@ def sliceArrayAssign(arr, index, val):
239266
self.assertRaises(NotImplementedError, sliceArrayAssign, a, 1, 1)
240267

241268
def test_base1(self):
269+
# For positive base arrays, indices are indexing elements directly (in absolute terms)
270+
# rather than relative form the base.
271+
# Negative indices are indexing relative form the end.
272+
242273
# 1-based 2x2 matrix
243274
arr = System.Array.CreateInstance(str, (2,2), (1,1))
244275

@@ -267,6 +298,38 @@ def test_base1(self):
267298
self.assertEqual(arr.GetValue(System.Array[System.Int32]((2,1))), "b_2,1")
268299
self.assertEqual(arr.GetValue(System.Array[System.Int32]((2,2))), "b_2,2")
269300

301+
def test_base_negative(self):
302+
# For negative base arrays, negative indices are indexing elements directly (like non negative indices)
303+
# rather than indexing relative from the end.
304+
305+
# 2-dim array [-1, 0, 1] x [-1, 0, 1]
306+
arr = System.Array.CreateInstance(str, (3,3), (-1,-1))
307+
for i in range(-1, 2):
308+
for j in range(-1, 2):
309+
arr[i, j] = "a_%d,%d" % (i, j)
310+
311+
for i in range(-1, 2):
312+
for j in range(-1, 2):
313+
self.assertEqual(arr[i, j], "a_%d,%d" % (i, j))
314+
315+
# test that VauleError is raised when the index is out of range
316+
self.assertRaises(IndexError, lambda: arr[-2, 0])
317+
self.assertRaises(IndexError, lambda: arr[2, 0])
318+
self.assertRaises(IndexError, lambda: arr[0, -2])
319+
self.assertRaises(IndexError, lambda: arr[0, 2])
320+
321+
# test slice indexing
322+
# 1-dim array [-1, 0, 1]
323+
arr1 = System.Array.CreateInstance(int, (3,), (-1,))
324+
for i in range(-1, 2):
325+
arr1[i] = i
326+
self.assertEqual(arr1[-1:1], System.Array[int]((-1, 0)))
327+
self.assertEqual(arr1[-2:1], System.Array[int]((-1, 0)))
328+
self.assertEqual(arr1[0:], System.Array[int]((0, 1)))
329+
self.assertEqual(arr1[:1], System.Array[int]((-1, 0)))
330+
self.assertEqual(arr1[:], System.Array[int]((-1, 0, 1)))
331+
self.assertEqual(arr1[:-2], System.Array[int](0))
332+
270333
def test_array_type(self):
271334

272335
def type_helper(array_type, instance):

0 commit comments

Comments
 (0)