implement correct slicing for setslice and delslice
authorStefan Behnel <stefan_ml@behnel.de>
Sat, 16 Mar 2013 15:24:27 +0000 (16:24 +0100)
committerStefan Behnel <stefan_ml@behnel.de>
Sat, 16 Mar 2013 15:24:27 +0000 (16:24 +0100)
--HG--
rename : tests/compile/delslice.pyx => tests/run/delslice.py

Cython/Compiler/ExprNodes.py
Cython/Utility/ObjectHandling.c
tests/compile/delslice.pyx [deleted file]
tests/run/delslice.py [new file with mode: 0644]

index 550da92..894842c 100755 (executable)
@@ -3670,21 +3670,8 @@ class SliceIndexNode(ExprNode):
         elif self.type is py_object_type:
             code.globalstate.use_utility_code(
                 UtilityCode.load_cached("GetObjectSlice", "ObjectHandling.c"))
-            has_c_start, c_start, py_start = False, '0', 'NULL'
-            if self.start:
-                has_c_start = not self.start.type.is_pyobject
-                if has_c_start:
-                    c_start = self.start.result()
-                else:
-                    py_start = '&%s' % self.start.py_result()
-            has_c_stop, c_stop, py_stop = False, '0', 'NULL'
-            if self.stop:
-                has_c_stop = not self.stop.type.is_pyobject
-                if has_c_stop:
-                    c_stop = self.stop.result()
-                else:
-                    py_stop = '&%s' % self.stop.py_result()
-            py_slice = self.slice and '&%s' % self.slice.py_result() or 'NULL'
+            (has_c_start, has_c_stop, c_start, c_stop,
+             py_start, py_stop, py_slice) = self.get_slice_config()
             code.putln(
                 "%s = __Pyx_PySequence_GetObjectSlice(%s, %s, %s, %s, %s, %s, %d, %d); %s" % (
                     result,
@@ -3717,12 +3704,17 @@ class SliceIndexNode(ExprNode):
     def generate_assignment_code(self, rhs, code):
         self.generate_subexpr_evaluation_code(code)
         if self.type.is_pyobject:
+            code.globalstate.use_utility_code(
+                UtilityCode.load_cached("SetObjectSlice", "ObjectHandling.c"))
+            (has_c_start, has_c_stop, c_start, c_stop,
+             py_start, py_stop, py_slice) = self.get_slice_config()
             code.put_error_if_neg(self.pos,
-                "__Pyx_PySequence_SetSlice(%s, %s, %s, %s)" % (
+                "__Pyx_PySequence_SetObjectSlice(%s, %s, %s, %s, %s, %s, %s, %d, %d)" % (
                     self.base.py_result(),
-                    self.start_code(),
-                    self.stop_code(),
-                    rhs.py_result()))
+                    rhs.py_result(),
+                    c_start, c_stop,
+                    py_start, py_stop, py_slice,
+                    has_c_start, has_c_stop))
         else:
             start_offset = ''
             if self.start:
@@ -3754,14 +3746,38 @@ class SliceIndexNode(ExprNode):
                   "Deleting slices is only supported for Python types, not '%s'." % self.type)
             return
         self.generate_subexpr_evaluation_code(code)
+        code.globalstate.use_utility_code(
+            UtilityCode.load_cached("SetObjectSlice", "ObjectHandling.c"))
+        (has_c_start, has_c_stop, c_start, c_stop,
+         py_start, py_stop, py_slice) = self.get_slice_config()
         code.put_error_if_neg(self.pos,
-            "__Pyx_PySequence_DelSlice(%s, %s, %s)" % (
+            "__Pyx_PySequence_DelObjectSlice(%s, %s, %s, %s, %s, %s, %d, %d)" % (
                 self.base.py_result(),
-                self.start_code(),
-                self.stop_code()))
+                c_start, c_stop,
+                py_start, py_stop, py_slice,
+                has_c_start, has_c_stop))
         self.generate_subexpr_disposal_code(code)
         self.free_subexpr_temps(code)
 
+    def get_slice_config(self):
+        has_c_start, c_start, py_start = False, '0', 'NULL'
+        if self.start:
+            has_c_start = not self.start.type.is_pyobject
+            if has_c_start:
+                c_start = self.start.result()
+            else:
+                py_start = '&%s' % self.start.py_result()
+        has_c_stop, c_stop, py_stop = False, '0', 'NULL'
+        if self.stop:
+            has_c_stop = not self.stop.type.is_pyobject
+            if has_c_stop:
+                c_stop = self.stop.result()
+            else:
+                py_stop = '&%s' % self.stop.py_result()
+        py_slice = self.slice and '&%s' % self.slice.py_result() or 'NULL'
+        return (has_c_start, has_c_stop, c_start, c_stop,
+                py_start, py_stop, py_slice)
+
     def generate_slice_guard_code(self, code, target_size):
         if not self.base.type.is_array:
             return
index 1ac5cc5..3d3729a 100644 (file)
@@ -543,6 +543,109 @@ static CYTHON_INLINE PyObject* __Pyx_PySequence_GetObjectSlice(
     return NULL;
 }
 
+/////////////// SetObjectSlice.proto ///////////////
+
+#define __Pyx_PySequence_DelObjectSlice(obj, cstart, cstop, py_start, py_stop, py_slice, has_cstart, has_cstop) \
+    __Pyx_PySequence_SetObjectSlice(obj, (PyObject*)NULL, cstart, cstop, py_start, py_stop, py_slice, has_cstart, has_cstop)
+
+// we pass pointer addresses to show the C compiler what is NULL and what isn't
+static CYTHON_INLINE int __Pyx_PySequence_SetObjectSlice(
+        PyObject* obj, PyObject* value, Py_ssize_t cstart, Py_ssize_t cstop,
+        PyObject** py_start, PyObject** py_stop, PyObject** py_slice,
+        int has_cstart, int has_cstop);
+
+/////////////// SetObjectSlice ///////////////
+
+static CYTHON_INLINE int __Pyx_PySequence_SetObjectSlice(
+        PyObject* obj, PyObject* value, Py_ssize_t cstart, Py_ssize_t cstop,
+        PyObject** _py_start, PyObject** _py_stop, PyObject** _py_slice,
+        int has_cstart, int has_cstop) {
+    PyMappingMethods* mp;
+#if PY_MAJOR_VERSION < 3
+    PySequenceMethods* ms = Py_TYPE(obj)->tp_as_sequence;
+    if (likely(ms && ms->sq_ass_slice)) {
+        if (!has_cstart) {
+            if (_py_start) {
+                cstart = __Pyx_PyIndex_AsSsize_t(*_py_start);
+                if ((cstart == (Py_ssize_t)-1) && PyErr_Occurred()) return -1;
+            } else
+                cstart = 0;
+        }
+        if (!has_cstop) {
+            if (_py_stop) {
+                cstop = __Pyx_PyIndex_AsSsize_t(*_py_stop);
+                if ((cstop == (Py_ssize_t)-1) && PyErr_Occurred()) return -1;
+            } else
+                cstop = PY_SSIZE_T_MAX;
+        }
+        if (unlikely((cstart < 0) | (cstop < 0)) && likely(ms->sq_length)) {
+            Py_ssize_t l = ms->sq_length(obj);
+            if (likely(l >= 0)) {
+                if (cstop < 0) {
+                    cstop += l;
+                    if (cstop < 0) cstop = 0;
+                }
+                if (cstart < 0) {
+                    cstart += l;
+                    if (cstart < 0) cstart = 0;
+                }
+            } else {
+                // if length > max(Py_ssize_t), maybe the object can wrap around itself?
+                if (PyErr_ExceptionMatches(PyExc_OverflowError))
+                    PyErr_Clear();
+                else
+                    return -1;
+            }
+        }
+        return ms->sq_ass_slice(obj, cstart, cstop, value);
+    }
+#endif
+    mp = Py_TYPE(obj)->tp_as_mapping;
+    if (likely(mp && mp->mp_ass_subscript)) {
+        PyObject *py_slice, *py_start, *py_stop;
+        int result;
+        if (_py_slice) {
+            py_slice = *_py_slice;
+        } else {
+            PyObject* owned_start = NULL;
+            PyObject* owned_stop = NULL;
+            if (_py_start) {
+                py_start = *_py_start;
+            } else {
+                if (has_cstart) {
+                    owned_start = py_start = PyInt_FromSsize_t(cstart);
+                    if (unlikely(!py_start)) return -1;
+                } else
+                    py_start = Py_None;
+            }
+            if (_py_stop) {
+                py_stop = *_py_stop;
+            } else {
+                if (has_cstop) {
+                    owned_stop = py_stop = PyInt_FromSsize_t(cstop);
+                    if (unlikely(!py_stop)) {
+                        Py_XDECREF(owned_start);
+                        return -1;
+                    }
+                } else
+                    py_stop = Py_None;
+            }
+            py_slice = PySlice_New(py_start, py_stop, Py_None);
+            Py_XDECREF(owned_start);
+            Py_XDECREF(owned_stop);
+            if (unlikely(!py_slice)) return -1;
+        }
+        result = mp->mp_ass_subscript(obj, py_slice, value);
+        if (!_py_slice) {
+            Py_DECREF(py_slice);
+        }
+        return result;
+    }
+    PyErr_Format(PyExc_TypeError,
+        "'%.200s' object is unsliceable", Py_TYPE(obj)->tp_name);
+    return -1;
+}
+
 /////////////// SliceTupleAndList.proto ///////////////
 
 #if CYTHON_COMPILING_IN_CPYTHON
diff --git a/tests/compile/delslice.pyx b/tests/compile/delslice.pyx
deleted file mode 100644 (file)
index 5d7c9b3..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-# mode: compile
-
-cdef void spam():
-    cdef object x
-    del x[17:42]
-
-spam()
diff --git a/tests/run/delslice.py b/tests/run/delslice.py
new file mode 100644 (file)
index 0000000..4861cca
--- /dev/null
@@ -0,0 +1,123 @@
+# mode: run
+# tag: del, slice
+
+def del_constant_start_stop(x):
+    """
+    >>> l = [1,2,3,4]
+    >>> del_constant_start_stop(l)
+    [1, 2]
+
+    >>> l = [1,2,3,4,5,6,7]
+    >>> del_constant_start_stop(l)
+    [1, 2, 7]
+    """
+    del x[2:6]
+    return x
+
+
+def del_start(x, start):
+    """
+    >>> l = [1,2,3,4]
+    >>> del_start(l, 2)
+    [1, 2]
+
+    >>> l = [1,2,3,4,5,6,7]
+    >>> del_start(l, 20)
+    [1, 2, 3, 4, 5, 6, 7]
+    >>> del_start(l, 8)
+    [1, 2, 3, 4, 5, 6, 7]
+    >>> del_start(l, 4)
+    [1, 2, 3, 4]
+
+    >>> del_start(l, -2)
+    [1, 2]
+    >>> l
+    [1, 2]
+    >>> del_start(l, -2)
+    []
+    >>> del_start(l, 2)
+    []
+    >>> del_start(l, -2)
+    []
+    >>> del_start(l, 20)
+    []
+
+    >>> del_start([1,2,3,4], -20)
+    []
+    >>> del_start([1,2,3,4], 0)
+    []
+    """
+    del x[start:]
+    return x
+
+
+def del_stop(x, stop):
+    """
+    >>> l = [1,2,3,4]
+    >>> del_stop(l, 2)
+    [3, 4]
+
+    >>> l = [1,2,3,4,5,6,7]
+    >>> del_stop(l, -20)
+    [1, 2, 3, 4, 5, 6, 7]
+    >>> del_stop(l, -8)
+    [1, 2, 3, 4, 5, 6, 7]
+    >>> del_stop(l, -4)
+    [4, 5, 6, 7]
+
+    >>> del_stop(l, -2)
+    [6, 7]
+    >>> l
+    [6, 7]
+    >>> del_stop(l, -2)
+    [6, 7]
+    >>> del_stop(l, 2)
+    []
+    >>> del_stop(l, -2)
+    []
+    >>> del_stop(l, 20)
+    []
+
+    >>> del_stop([1,2,3,4], -20)
+    [1, 2, 3, 4]
+    >>> del_stop([1,2,3,4], 0)
+    [1, 2, 3, 4]
+    """
+    del x[:stop]
+    return x
+
+
+def del_start_stop(x, start, stop):
+    """
+    >>> l = [1,2,3,4]
+    >>> del_start_stop(l, 0, 2)
+    [3, 4]
+    >>> l
+    [3, 4]
+
+    >>> l = [1,2,3,4,5,6,7]
+    >>> del_start_stop(l, -1, -20)
+    [1, 2, 3, 4, 5, 6, 7]
+    >>> del_start_stop(l, -20, -8)
+    [1, 2, 3, 4, 5, 6, 7]
+    >>> del_start_stop(l, -6, -4)
+    [1, 4, 5, 6, 7]
+
+    >>> del_start_stop(l, -20, -2)
+    [6, 7]
+    >>> l
+    [6, 7]
+    >>> del_start_stop(l, -2, 1)
+    [7]
+    >>> del_start_stop(l, -2, 3)
+    []
+    >>> del_start_stop(l, 2, 4)
+    []
+
+    >>> del_start_stop([1,2,3,4], 20, -20)
+    [1, 2, 3, 4]
+    >>> del_start_stop([1,2,3,4], 0, 0)
+    [1, 2, 3, 4]
+    """
+    del x[start:stop]
+    return x