Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/CPyCppyy/src/Pythonize.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,8 @@ static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash
static const ptrdiff_t PS_FLAG_ADDR = 11; // id.
static const ptrdiff_t PS_COLL_ADDR = 13; // id.

PyObject* STLIterNext(PyObject* self); // defined below; used by STLSequenceIter

PyObject* LLSequenceIter(PyObject* self)
{
// Implement python's __iter__ for low level views used through STL-type begin()/end()
Expand Down Expand Up @@ -992,6 +994,25 @@ PyObject* STLSequenceIter(PyObject* self)
PyObject* end = PyObject_CallMethodNoArgs(self, PyStrings::gEnd);
if (end) {
if (CPPInstance_Check(iter)) {
// Guarantee the returned iterator implements Python's iterator protocol.
// The deferred, name-based __next__ install can miss when an iterator's
// canonical return-type spelling and its scoped-final-name diverge
// (e.g libc++'s std::__1::__wrap_iter on macOS) leaving the type without
// __next__, so iter() rejects it. begin()/end() resolving here proves
// this is an STL forward iterator, so install the protocol now if it is still absent
PyTypeObject* itype = Py_TYPE(iter);
if (!PyIter_Check(iter)) { // no tp_iternext, or the
// _PyObject_NextNotImplemented sentinel
itype->tp_iternext = (iternextfunc)STLIterNext;
Utility::AddToClass((PyObject*)itype, CPPYY__next__,
(PyCFunction)STLIterNext, METH_NOARGS);
if (!itype->tp_iter) {
itype->tp_iter = (getiterfunc)PyObject_SelfIter;
Utility::AddToClass((PyObject*)itype, "__iter__",
(PyCFunction)PyObject_SelfIter, METH_NOARGS);
}
PyType_Modified(itype);
}
// use the data member cache to store extra state on the iterator object,
// without it being visible on the Python side
auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache();
Expand Down
2 changes: 1 addition & 1 deletion src/CPyCppyy/src/Utility.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ std::string CPyCppyy::Utility::CT2CppNameS(PyObject* pytc, bool allow_voidp)
case 'd': name = "double"; break;
case 'g': name = "long double"; break;
case 'z': name = "const char*"; break;
default: name = (allow_voidp ? "void*" : nullptr); break;
default: if (allow_voidp) name = "void*"; break;
}
}

Expand Down
9 changes: 8 additions & 1 deletion test/test_datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2403,7 +2403,7 @@ def test53_basic_nanoseconds_goodness(self):
with raises(TypeError):
ns53.f1(1000)

@mark.xfail(reason="optional on enum uint8_t is broken")
@mark.xfail(condition= IS_MAC, reason="std::optional<std::string>::value_or rvalue conversion fails on OS X clang-repl (InstanceMoveConverter)")
def test54_optional_use(self):
import cppyy
cppyy.cppdef("""
Expand All @@ -2418,6 +2418,9 @@ def test54_optional_use(self):
enum class Tec : char {ZERO, ONE, TWO};
std::optional<Tec> tec;

enum class Tei8 : int8_t {ZERO, ONE, TWO};
std::optional<Tei8> tei8;

enum class Teu8 : uint8_t {ZERO, ONE, TWO};
std::optional<Teu8> teu8;
}
Expand All @@ -2439,6 +2442,10 @@ def test54_optional_use(self):
ns54.tec = ns54.Tec.TWO
assert ns54.tec == ns54.Tec.TWO

assert ns54.tei8.value_or(ns54.Tei8.TWO) == ns54.Tei8.TWO
ns54.tei8 = ns54.Tei8.TWO
assert ns54.tei8 == ns54.Tei8.TWO

assert ns54.teu8.value_or(ns54.Teu8.TWO) == ns54.Teu8.TWO
ns54.teu8 = ns54.Teu8.TWO
assert ns54.teu8 == ns54.Teu8.TWO
Expand Down
66 changes: 64 additions & 2 deletions test/test_lowlevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,8 @@ def py2c(pyargs):

voidpp = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_void_p))
for i in range(argc.value):
cppyy.ll.free(ctypes.cast(voidpp[i], ctypes.c_void_p))
cppyy.ll.free(ptr)
cppyy.ll.array_delete(ctypes.cast(voidpp[i], ctypes.POINTER(ctypes.c_ubyte)))
cppyy.ll.array_delete['char*'](ctypes.cast(ptr, ctypes.POINTER(ctypes.c_char_p)))

def test12_null_array(self):
"""Null low level view as empty list"""
Expand Down Expand Up @@ -534,6 +534,68 @@ def test16_addressof_nullptr(self):
assert cppyy.addressof(gbl.LLV.ptr_x)
assert cppyy.addressof(gbl.LLV.ptr_null) == 0

def test17_array_delete_multidim(self):
"""Free a multidimensional (jagged) heap array with ll.array_delete"""

import cppyy, ctypes
import cppyy.ll

# C++ allocates a jagged 2D array (array of pointers to rows)
cppyy.cppdef("""\
namespace ArrayDeleteMD {
void make2d(int& n, int& m, double**& a) {
n = 2; m = 3;
a = new double*[n];
for (int i = 0; i < n; ++i) {
a[i] = new double[m];
for (int j = 0; j < m; ++j) a[i][j] = 10.*i + j;
}
} }""")

n = ctypes.c_int(0); m = ctypes.c_int(0)
ptr = ctypes.c_void_p()
cppyy.gbl.ArrayDeleteMD.make2d(n, m, ptr)

assert n.value == 2
assert m.value == 3
rows = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_void_p))
for i in range(n.value):
row = ctypes.cast(rows[i], ctypes.POINTER(ctypes.c_double))
for j in range(m.value):
assert row[j] == 10.*i + j

# delete[] each row, then the (outer) array of row pointers
for i in range(n.value):
cppyy.ll.array_delete(ctypes.cast(rows[i], ctypes.POINTER(ctypes.c_double)))
cppyy.ll.array_delete['void'](ptr)

def test18_array_delete_fixed(self):
"""Free a fixed-size contiguous multidimensional heap array"""

import cppyy, ctypes
import cppyy.ll

# C++ allocates a contiguous 2D array (single new int[3][4])
cppyy.cppdef("""\
namespace ArrayDeleteFixed {
intptr_t make(int& r, int& c) {
r = 3; c = 4;
int (*a)[4] = new int[3][4];
for (int i = 0; i < r; ++i)
for (int j = 0; j < c; ++j) a[i][j] = c*i + j;
return (intptr_t)a;
} }""")

r = ctypes.c_int(0); c = ctypes.c_int(0)
addr = cppyy.gbl.ArrayDeleteFixed.make(r, c)

blk = ctypes.cast(addr, ctypes.POINTER(ctypes.c_int))
for k in range(r.value * c.value):
assert blk[k] == k

# a single contiguous allocation is released with a single delete[]
cppyy.ll.array_delete(ctypes.cast(ctypes.c_void_p(addr), ctypes.POINTER(ctypes.c_int)))

class TestMULTIDIMARRAYS:
def setup_class(cls):
import cppyy
Expand Down
Loading