diff --git a/mypyc/lib-rt/list_ops.c b/mypyc/lib-rt/list_ops.c index 31a0d5cec7d5..c611907fb601 100644 --- a/mypyc/lib-rt/list_ops.c +++ b/mypyc/lib-rt/list_ops.c @@ -235,19 +235,51 @@ void CPyList_SetItemUnsafe(PyObject *list, Py_ssize_t index, PyObject *value) { PyList_SET_ITEM(list, index, value); } -PyObject *CPyList_PopLast(PyObject *obj) +#ifdef Py_GIL_DISABLED +// The original optimized list.pop implementation doesn't work on free-threaded +// builds, so provide an alternative that is a bit slower but works. +// +// Note that this implementation isn't intended to be atomic. +static inline PyObject *list_pop_index(PyObject *list, Py_ssize_t index) { + PyObject *item = PyList_GetItemRef(list, index); + if (item == NULL) { + return NULL; + } + if (PySequence_DelItem(list, index) < 0) { + Py_DECREF(item); + return NULL; + } + return item; +} +#endif + +PyObject *CPyList_PopLast(PyObject *list) { +#ifdef Py_GIL_DISABLED + // The other implementation causes segfaults on a free-threaded Python 3.14b4 build. + Py_ssize_t index = PyList_GET_SIZE(list) - 1; + return list_pop_index(list, index); +#else // I tried a specalized version of pop_impl for just removing the // last element and it wasn't any faster in microbenchmarks than // the generic one so I ditched it. - return list_pop_impl((PyListObject *)obj, -1); + return list_pop_impl((PyListObject *)list, -1); +#endif } PyObject *CPyList_Pop(PyObject *obj, CPyTagged index) { if (CPyTagged_CheckShort(index)) { Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); +#ifdef Py_GIL_DISABLED + // We must use a slower implementation on free-threaded builds. + if (n < 0) { + n += PyList_GET_SIZE(obj); + } + return list_pop_index(obj, n); +#else return list_pop_impl((PyListObject *)obj, n); +#endif } else { PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG); return NULL; diff --git a/mypyc/primitives/list_ops.py b/mypyc/primitives/list_ops.py index 516d9e1a4e02..b9d20a25bea3 100644 --- a/mypyc/primitives/list_ops.py +++ b/mypyc/primitives/list_ops.py @@ -219,7 +219,7 @@ ) # list.pop(index) -list_pop = method_op( +method_op( name="pop", arg_types=[list_rprimitive, int_rprimitive], return_type=object_rprimitive, diff --git a/mypyc/test-data/run-lists.test b/mypyc/test-data/run-lists.test index 03d5741b9eca..54bcc0384604 100644 --- a/mypyc/test-data/run-lists.test +++ b/mypyc/test-data/run-lists.test @@ -150,7 +150,9 @@ print(primes(13)) \[0, 0, 1, 1] \[0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1] -[case testListBuild] +[case testListPrimitives] +from testutil import assertRaises + def test_list_build() -> None: # Currently LIST_BUILDING_EXPANSION_THRESHOLD equals to 10 # long list built by list_build_op @@ -169,9 +171,6 @@ def test_list_build() -> None: l3.append('a') assert l3 == ['a'] -[case testListPrims] -from typing import List - def test_append() -> None: l = [1, 2] l.append(10) @@ -189,10 +188,28 @@ def test_pop_last() -> None: def test_pop_index() -> None: l = [1, 2, 10, 3] - l.pop(2) + assert l.pop(2) == 10 assert l == [1, 2, 3] - l.pop(-2) + assert l.pop(-2) == 2 assert l == [1, 3] + assert l.pop(-2) == 1 + assert l.pop(0) == 3 + assert l == [] + l = [int() + 1000, int() + 1001, int() + 1002] + assert l.pop(0) == 1000 + assert l.pop(-1) == 1002 + assert l == [1001] + +def test_pop_index_errors() -> None: + l = [int() + 1000] + with assertRaises(IndexError): + l.pop(1) + with assertRaises(IndexError): + l.pop(-2) + with assertRaises(OverflowError): + l.pop(1 << 100) + with assertRaises(OverflowError): + l.pop(-(1 << 100)) def test_count() -> None: l = [1, 3]