Skip to content

Commit 018f351

Browse files
committed
Sequence methods, fixing sq_ass_item with some very weird index manipulation for negative indexes.
1 parent 3f13196 commit 018f351

File tree

3 files changed

+163
-109
lines changed

3 files changed

+163
-109
lines changed

doc/sphinx/source/new_types.rst

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -942,24 +942,79 @@ In ``src/cpy/Object/cSeqObject.c``:
942942

943943
.. code-block:: c
944944
945-
/** Returns a new reference to an indexed item in a sequence. */
946-
static PyObject *
947-
SequenceLongObject_sq_item(PyObject *self, Py_ssize_t index) {
945+
static int
946+
SequenceLongObject_sq_ass_item(PyObject *self, Py_ssize_t index, PyObject *value) {
947+
fprintf(
948+
stdout, "%s()#%d: self=%p index=%zd value=%p\n",
949+
__FUNCTION__, __LINE__, (void *) self, index, (void *) value
950+
);
948951
Py_ssize_t my_index = index;
949952
if (my_index < 0) {
950953
my_index += SequenceLongObject_sq_length(self);
951954
}
952955
// Corner case example: len(self) == 0 and index < 0
956+
fprintf(
957+
stdout, "%s()#%d: len=%zd index=%zd my_index=%zd\n", __FUNCTION__, __LINE__,
958+
SequenceLongObject_sq_length(self), index, my_index
959+
);
953960
if (my_index < 0 || my_index >= SequenceLongObject_sq_length(self)) {
954961
PyErr_Format(
955962
PyExc_IndexError,
956963
"Index %ld is out of range for length %ld",
957964
index,
958965
SequenceLongObject_sq_length(self)
959966
);
960-
return NULL;
967+
return -1;
961968
}
962-
return PyLong_FromLong(((SequenceLongObject *) self)->array_long[my_index]);
969+
if (value != NULL) {
970+
/* Just set the value. */
971+
if (!PyLong_Check(value)) {
972+
PyErr_Format(
973+
PyExc_TypeError,
974+
"sq_ass_item value needs to be an int, not type %s",
975+
Py_TYPE(value)->tp_name
976+
);
977+
return -1;
978+
}
979+
((SequenceLongObject *) self)->array_long[my_index] = PyLong_AsLong(value);
980+
} else {
981+
/* Delete the value. */
982+
/* For convenience. */
983+
SequenceLongObject *self_as_slo = (SequenceLongObject *) self;
984+
/* Special case: deleting the only item in the array. */
985+
if (self_as_slo->size == 1) {
986+
fprintf(stdout, "%s()#%d: deleting empty index\n", __FUNCTION__, __LINE__);
987+
free(self_as_slo->array_long);
988+
self_as_slo->array_long = NULL;
989+
self_as_slo->size = 0;
990+
} else {
991+
/* Delete the value and re-compose the array. */
992+
fprintf(stdout, "%s()#%d: deleting index=%zd\n", __FUNCTION__, __LINE__, index);
993+
long *new_array = malloc((self_as_slo->size - 1) * sizeof(long));
994+
if (!new_array) {
995+
PyErr_Format(
996+
PyExc_MemoryError,
997+
"sq_ass_item can not allocate new array. %s#%d",
998+
__FILE__, __LINE__
999+
);
1000+
return -1;
1001+
}
1002+
/* Copy up to the index. */
1003+
Py_ssize_t index_new_array = 0;
1004+
for (Py_ssize_t i = 0; i < my_index; ++i, ++index_new_array) {
1005+
new_array[index_new_array] = self_as_slo->array_long[i];
1006+
}
1007+
/* Copy past the index. */
1008+
for (Py_ssize_t i = my_index + 1; i < self_as_slo->size; ++i, ++index_new_array) {
1009+
new_array[index_new_array] = self_as_slo->array_long[i];
1010+
}
1011+
1012+
free(self_as_slo->array_long);
1013+
self_as_slo->array_long = new_array;
1014+
--self_as_slo->size;
1015+
}
1016+
}
1017+
return 0;
9631018
}
9641019
9651020
Tests
@@ -971,52 +1026,7 @@ Tests are in ``tests/unit/test_c_seqobject.py`` which includes failure modes:
9711026
9721027
from cPyExtPatt import cSeqObject
9731028
974-
@pytest.mark.parametrize(
975-
'initial_sequence, index, expected',
976-
(
977-
(
978-
[7, 4, 1, ], 0, 7,
979-
),
980-
(
981-
[7, 4, 1, ], 1, 4,
982-
),
983-
(
984-
[7, 4, 1, ], 2, 1,
985-
),
986-
(
987-
[7, 4, 1, ], -1, 1,
988-
),
989-
(
990-
[7, 4, 1, ], -2, 4,
991-
),
992-
(
993-
[7, 4, 1, ], -3, 7,
994-
),
995-
)
996-
)
997-
def test_SequenceLongObject_item(initial_sequence, index, expected):
998-
obj = cSeqObject.SequenceLongObject(initial_sequence)
999-
assert obj[index] == expected
1000-
1001-
@pytest.mark.parametrize(
1002-
'initial_sequence, index, expected',
1003-
(
1004-
(
1005-
[], 0, 'Index 0 is out of range for length 0',
1006-
),
1007-
(
1008-
[], -1, 'Index -1 is out of range for length 0',
1009-
),
1010-
(
1011-
[1, ], 2, 'Index 2 is out of range for length 1',
1012-
),
1013-
)
1014-
)
1015-
def test_SequenceLongObject_item_raises(initial_sequence, index, expected):
1016-
obj = cSeqObject.SequenceLongObject(initial_sequence)
1017-
with pytest.raises(IndexError) as err:
1018-
obj[index]
1019-
assert err.value.args[0] == expected
1029+
pass
10201030
10211031
10221032

src/cpy/Object/cSeqObject.c

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,24 @@ SequenceLongObject_sq_ass_item(PyObject *self, Py_ssize_t index, PyObject *value
222222
stdout, "%s()#%d: self=%p index=%zd value=%p\n",
223223
__FUNCTION__, __LINE__, (void *) self, index, (void *) value
224224
);
225+
/* This is very weird. */
226+
if (index < 0) {
227+
fprintf(
228+
stdout, "%s()#%d: Fixing index index=%zd to %zd\n", __FUNCTION__, __LINE__,
229+
index, index - SequenceLongObject_sq_length(self)
230+
);
231+
index -= SequenceLongObject_sq_length(self);
232+
}
233+
/* Isn't it? */
225234
Py_ssize_t my_index = index;
226235
if (my_index < 0) {
227236
my_index += SequenceLongObject_sq_length(self);
228237
}
229238
// Corner case example: len(self) == 0 and index < 0
239+
fprintf(
240+
stdout, "%s()#%d: len=%zd index=%zd my_index=%zd\n", __FUNCTION__, __LINE__,
241+
SequenceLongObject_sq_length(self), index, my_index
242+
);
230243
if (my_index < 0 || my_index >= SequenceLongObject_sq_length(self)) {
231244
PyErr_Format(
232245
PyExc_IndexError,
@@ -269,38 +282,16 @@ SequenceLongObject_sq_ass_item(PyObject *self, Py_ssize_t index, PyObject *value
269282
);
270283
return -1;
271284
}
272-
/* memcpy parameters. */
273-
void *dest = NULL;
274-
void *src = NULL;
275-
size_t count = 0;
276-
277-
/* memcpy across to the new array, firstly up to the index. */
278-
dest = new_array;
279-
src = self_as_slo->array_long;
280-
count = my_index * sizeof(long);
281-
fprintf(stdout, "%s()#%d: First: dest=%p src=%p count=%zu\n", __FUNCTION__, __LINE__, dest, src, count);
282-
if (memcpy(dest, src, count) != dest) {
283-
PyErr_Format(
284-
PyExc_MemoryError,
285-
"sq_ass_item can not memcpy into new array. %s#%d",
286-
__FILE__, __LINE__
287-
);
288-
return -1;
285+
/* Copy up to the index. */
286+
Py_ssize_t index_new_array = 0;
287+
for (Py_ssize_t i = 0; i < my_index; ++i, ++index_new_array) {
288+
new_array[index_new_array] = self_as_slo->array_long[i];
289289
}
290-
/* memcpy from the index to the end. */
291-
dest = new_array + count;
292-
src = self_as_slo->array_long + count;
293-
/* Example size=4, index=2 copy one value */
294-
count = (self_as_slo->size - my_index - 1) * sizeof(long);
295-
fprintf(stdout, "%s()#%d: Next: dest=%p src=%p count=%zu\n", __FUNCTION__, __LINE__, dest, src, count);
296-
if (memcpy(dest, src, count) != dest) {
297-
PyErr_Format(
298-
PyExc_MemoryError,
299-
"sq_ass_item can not memcpy into new array. %s#%d",
300-
__FILE__, __LINE__
301-
);
302-
return -1;
290+
/* Copy past the index. */
291+
for (Py_ssize_t i = my_index + 1; i < self_as_slo->size; ++i, ++index_new_array) {
292+
new_array[index_new_array] = self_as_slo->array_long[i];
303293
}
294+
304295
free(self_as_slo->array_long);
305296
self_as_slo->array_long = new_array;
306297
--self_as_slo->size;
@@ -309,15 +300,15 @@ SequenceLongObject_sq_ass_item(PyObject *self, Py_ssize_t index, PyObject *value
309300
return 0;
310301
}
311302

312-
PySequenceMethods SequenceLongObject_sequence_methods = {
313-
.sq_length = &SequenceLongObject_sq_length,
314-
.sq_concat = &SequenceLongObject_sq_concat,
315-
.sq_repeat = &SequenceLongObject_sq_repeat,
316-
.sq_item = &SequenceLongObject_sq_item,
317-
.sq_ass_item = &SequenceLongObject_sq_ass_item,
318-
.sq_contains = NULL,
319-
.sq_inplace_concat = NULL,
320-
.sq_inplace_repeat = NULL,
303+
static PySequenceMethods SequenceLongObject_sequence_methods = {
304+
.sq_length = (lenfunc)SequenceLongObject_sq_length,
305+
.sq_concat = (binaryfunc)SequenceLongObject_sq_concat,
306+
.sq_repeat = (ssizeargfunc)SequenceLongObject_sq_repeat,
307+
.sq_item = (ssizeargfunc)SequenceLongObject_sq_item,
308+
.sq_ass_item = (ssizeobjargproc)SequenceLongObject_sq_ass_item,
309+
.sq_contains = (objobjproc)NULL,
310+
.sq_inplace_concat = (binaryfunc)NULL,
311+
.sq_inplace_repeat = (ssizeargfunc)NULL,
321312
};
322313

323314
static PyObject *

tests/unit/test_c_seqobject.py

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -156,47 +156,100 @@ def test_SequenceLongObject_item_raises(initial_sequence, index, expected):
156156
(
157157
[7, 4, 1, ], -1, 14, [7, 4, 14, ],
158158
),
159-
# (
160-
# [], 0, None, [],
161-
# ),
159+
)
160+
)
161+
def test_SequenceLongObject_setitem(initial_sequence, index, value, expected):
162+
obj = cSeqObject.SequenceLongObject(initial_sequence)
163+
obj[index] = value
164+
assert list(obj) == expected
165+
166+
167+
@pytest.mark.parametrize(
168+
'initial_sequence, index, expected',
169+
(
162170
(
163-
[7,], 0, None, [],
171+
[7, 4, 1, ], 3, 'Index 3 is out of range for length 3',
164172
),
165173
(
166-
[7,], -1, None, [],
174+
[7, 4, 1, ], -4, 'Index -4 is out of range for length 3',
167175
),
176+
)
177+
)
178+
def test_SequenceLongObject_setitem_raises(initial_sequence, index, expected):
179+
print()
180+
print(initial_sequence, index, expected)
181+
obj = cSeqObject.SequenceLongObject(initial_sequence)
182+
with pytest.raises(IndexError) as err:
183+
obj[index] = 100
184+
print(list(obj))
185+
assert err.value.args[0] == expected
186+
187+
188+
@pytest.mark.parametrize(
189+
'initial_sequence, index, expected',
190+
(
168191
(
169-
[7, 4, 1, ], 1, None, [7, 1, ],
192+
[7,], 0, [],
170193
),
171194
(
172-
[7, 4, ], 0, None, [4, ],
195+
[7,], -1, [],
196+
),
197+
(
198+
[7, 4, 1, ], 1, [7, 1, ],
199+
),
200+
(
201+
[7, 4, ], 0, [4, ],
173202
),
174203
)
175204
)
176-
def test_SequenceLongObject_setitem(initial_sequence, index, value, expected):
205+
def test_SequenceLongObject_delitem(initial_sequence, index, expected):
177206
obj = cSeqObject.SequenceLongObject(initial_sequence)
178-
if value is not None:
179-
obj[index] = value
180-
else:
181-
del obj[index]
207+
del obj[index]
182208
assert list(obj) == expected
183209

184210

185211
@pytest.mark.parametrize(
186-
'initial_sequence, index, value, expected',
212+
'initial_sequence, index, expected',
187213
(
188214
(
189-
[7, 4, 1, ], 1, None, [7, 1, ],
215+
[], 0, 'Index 0 is out of range for length 0',
216+
),
217+
(
218+
[], -1, 'Index -1 is out of range for length 0',
219+
),
220+
(
221+
[7,], 1, 'Index 1 is out of range for length 1',
222+
),
223+
(
224+
[7,], -3, 'Index -3 is out of range for length 1',
190225
),
191226
)
192227
)
193-
def test_SequenceLongObject_setitem_debug(initial_sequence, index, value, expected):
228+
def test_SequenceLongObject_delitem_raises(initial_sequence, index, expected):
229+
print()
230+
print(initial_sequence, index, expected)
194231
obj = cSeqObject.SequenceLongObject(initial_sequence)
195-
if value is not None:
196-
obj[index] = value
197-
else:
232+
print(list(obj))
233+
with pytest.raises(IndexError) as err:
198234
del obj[index]
199-
assert list(obj) == expected
235+
assert err.value.args[0] == expected
236+
237+
238+
# @pytest.mark.parametrize(
239+
# 'initial_sequence, index, value, expected',
240+
# (
241+
# (
242+
# [7, 4, 1, ], 1, None, [7, 1, ],
243+
# ),
244+
# )
245+
# )
246+
# def test_SequenceLongObject_setitem_debug(initial_sequence, index, value, expected):
247+
# obj = cSeqObject.SequenceLongObject(initial_sequence)
248+
# if value is not None:
249+
# obj[index] = value
250+
# else:
251+
# del obj[index]
252+
# assert list(obj) == expected
200253

201254

202255
# @pytest.mark.skipif(not (sys.version_info.minor < 7), reason='Python < 3.7')

0 commit comments

Comments
 (0)