2929#include < sstream>
3030#include < cstddef>
3131#include < string_view>
32+ #if __cplusplus >= 202002L
33+ #include < span>
34+ #endif
3235
3336// codecvt does not exist for gcc4.8.5 and is in principle deprecated; it is
3437// only used in py2 for char -> wchar_t conversion for std::wstring; if not
@@ -1292,7 +1295,8 @@ bool CPyCppyy::CStringConverter::SetArg(
12921295
12931296// verify (too long string will cause truncation, no crash)
12941297 if (fMaxSize != std::string::npos && fMaxSize < fBuffer .size ())
1295- PyErr_Warn (PyExc_RuntimeWarning, (char *)" string too long for char array (truncated)" );
1298+ if (PyErr_WarnEx (PyExc_RuntimeWarning, (char *)" string too long for char array (truncated)" , 1 ) < 0 )
1299+ return false ;
12961300
12971301 if (!ctxt->fPyContext ) {
12981302 // use internal buffer as workaround
@@ -1337,7 +1341,8 @@ bool CPyCppyy::CStringConverter::ToMemory(PyObject* value, void* address, PyObje
13371341
13381342// verify (too long string will cause truncation, no crash)
13391343 if (fMaxSize != std::string::npos && fMaxSize < (std::string::size_type)len)
1340- PyErr_Warn (PyExc_RuntimeWarning, (char *)" string too long for char array (truncated)" );
1344+ if (PyErr_WarnEx (PyExc_RuntimeWarning, (char *)" string too long for char array (truncated)" , 1 ) < 0 )
1345+ return false ;
13411346
13421347// if address is available, and it wasn't set by this converter, assume a byte-wise copy;
13431348// otherwise assume a pointer copy (this relies on the converter to be used for properties,
@@ -1414,7 +1419,8 @@ bool CPyCppyy::WCStringConverter::ToMemory(PyObject* value, void* address, PyObj
14141419
14151420// verify (too long string will cause truncation, no crash)
14161421 if (fMaxSize != std::wstring::npos && fMaxSize < (std::wstring::size_type)len)
1417- PyErr_Warn (PyExc_RuntimeWarning, (char *)" string too long for wchar_t array (truncated)" );
1422+ if (PyErr_WarnEx (PyExc_RuntimeWarning, (char *)" string too long for wchar_t array (truncated)" , 1 ) < 0 )
1423+ return false ;
14181424
14191425 Py_ssize_t res = -1 ;
14201426 if (fMaxSize != std::wstring::npos)
@@ -1473,7 +1479,10 @@ bool CPyCppyy::name##Converter::ToMemory(PyObject* value, void* address, PyObjec
14731479 \
14741480/* verify (too long string will cause truncation, no crash) */ \
14751481 if (fMaxSize != std::wstring::npos && maxbytes < len) { \
1476- PyErr_Warn (PyExc_RuntimeWarning, (char *)" string too long for " #type" array (truncated)" );\
1482+ if (PyErr_WarnEx (PyExc_RuntimeWarning, (char *)" string too long for " #type" array (truncated)" , 1 ) < 0 ) { \
1483+ Py_DECREF (bstr); \
1484+ return false ; \
1485+ } \
14771486 len = maxbytes; \
14781487 } \
14791488 \
@@ -1637,6 +1646,78 @@ bool CPyCppyy::VoidArrayConverter::ToMemory(PyObject* value, void* address, PyOb
16371646 return true ;
16381647}
16391648
1649+ #if __cplusplus >= 202002L
1650+
1651+ namespace CPyCppyy {
1652+
1653+ class StdSpanConverter : public InstanceConverter {
1654+ public:
1655+ StdSpanConverter (std::string const &typeName, Cppyy::TCppType_t klass, bool keepControl = false )
1656+ : InstanceConverter{klass, keepControl}, fTypeName {typeName}
1657+ {
1658+ }
1659+
1660+ ~StdSpanConverter ()
1661+ {
1662+ if (fHasBuffer ) {
1663+ PyBuffer_Release (&fBufinfo );
1664+ }
1665+ }
1666+
1667+ bool SetArg (PyObject *, Parameter &, CallContext * = nullptr ) override ;
1668+ bool HasState () override { return true ; }
1669+
1670+ private:
1671+ std::string fTypeName ;
1672+ std::span<std::size_t > fBuffer ;
1673+ bool fHasBuffer = false ;
1674+ Py_buffer fBufinfo ;
1675+ };
1676+
1677+ } // namespace CPyCppyy
1678+
1679+ // ----------------------------------------------------------------------------
1680+ bool CPyCppyy::StdSpanConverter::SetArg (PyObject *pyobject, Parameter ¶, CallContext *ctxt)
1681+ {
1682+ auto typecodeFound = Utility::TypecodeMap ().find (fTypeName );
1683+
1684+ // attempt to get buffer if the C++ type maps to a buffer type
1685+ if (typecodeFound == Utility::TypecodeMap ().end () || !PyObject_CheckBuffer (pyobject)) {
1686+ // Fall back to regular InstanceConverter
1687+ return this ->InstanceConverter ::SetArg (pyobject, para, ctxt);
1688+ }
1689+
1690+ Py_ssize_t buflen = 0 ;
1691+ char typecode = typecodeFound->second ;
1692+ memset (&fBufinfo , 0 , sizeof (Py_buffer));
1693+
1694+ if (PyObject_GetBuffer (pyobject, &fBufinfo , PyBUF_FORMAT) == 0 ) {
1695+ if (!strchr (fBufinfo .format , typecode)) {
1696+ PyErr_Format (PyExc_TypeError,
1697+ " buffer has incompatible type: expected '%c' for C++ type '%s', but got format '%s'" , typecode,
1698+ fTypeName .c_str (), fBufinfo .format ? fBufinfo .format : " <null>" );
1699+ PyBuffer_Release (&fBufinfo );
1700+ return false ;
1701+ }
1702+ buflen = Utility::GetBuffer (pyobject, typecode, 1 , para.fValue .fVoidp , false );
1703+ }
1704+
1705+ // ok if buffer exists (can't perform any useful size checks)
1706+ if (para.fValue .fVoidp && buflen != 0 ) {
1707+ // We assume the layout for any std::span<T> is the same, and just use
1708+ // std::span<std::size_t> as a placeholder. Not elegant, but works.
1709+ fBuffer = std::span<std::size_t >{(std::size_t *)para.fValue .fVoidp , static_cast <std::size_t >(buflen)};
1710+ fHasBuffer = true ;
1711+ para.fValue .fVoidp = &fBuffer ;
1712+ para.fTypeCode = ' V' ;
1713+ return true ;
1714+ }
1715+
1716+ return false ;
1717+ }
1718+
1719+ #endif // __cplusplus >= 202002L
1720+
16401721
16411722// ----------------------------------------------------------------------------
16421723#define CPPYY_IMPL_ARRAY_CONVERTER (name, ctype, type, code, suffix ) \
@@ -3177,11 +3258,25 @@ bool CPyCppyy::InitializerListConverter::SetArg(
31773258#endif
31783259}
31793260
3261+ namespace CPyCppyy {
3262+
3263+ // raising converter to take out overloads
3264+ class NotImplementedConverter : public Converter {
3265+ public:
3266+ NotImplementedConverter (PyObject *errorType, std::string const &message) : fErrorType {errorType}, fMessage {message} {}
3267+ bool SetArg (PyObject*, Parameter&, CallContext* = nullptr ) override ;
3268+ private:
3269+ PyObject *fErrorType ;
3270+ std::string fMessage ;
3271+ };
3272+
3273+ } // namespace CPyCppyy
3274+
31803275// ----------------------------------------------------------------------------
31813276bool CPyCppyy::NotImplementedConverter::SetArg (PyObject*, Parameter&, CallContext*)
31823277{
31833278// raise a NotImplemented exception to take a method out of overload resolution
3184- PyErr_SetString (PyExc_NotImplementedError, " this method cannot (yet) be called " );
3279+ PyErr_SetString (fErrorType , fMessage . c_str () );
31853280 return false ;
31863281}
31873282
@@ -3247,6 +3342,14 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim
32473342 const std::string& cpd = TypeManip::compound (resolvedType);
32483343 std::string realType = TypeManip::clean_type (resolvedType, false , true );
32493344
3345+ // mutable pointer references (T*&) are incompatible with Python's object model
3346+ if (cpd == " *&" ) {
3347+ return new NotImplementedConverter{PyExc_TypeError,
3348+ " argument type '" + resolvedType + " ' is not supported: non-const references to pointers (T*&) allow a"
3349+ " function to replace the pointer itself. Python cannot represent this safely. Consider changing the"
3350+ " C++ API to return the new pointer or use a wrapper" };
3351+ }
3352+
32503353// accept unqualified type (as python does not know about qualifiers)
32513354 h = gConvFactories .find ((isConst ? " const " : " " ) + realType + cpd);
32523355 if (h != gConvFactories .end ())
@@ -3331,6 +3434,31 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim
33313434 }
33323435 }
33333436
3437+ // FIXME: Taken from ROOT, update this to use CppInterOp for span check and extracting value type
3438+ #if __cplusplus >= 202002L
3439+ // -- special case: std::span
3440+ pos = resolvedType.find (" span<" );
3441+ if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ ||
3442+ pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */ ) {
3443+
3444+ auto pos1 = realType.find (' <' );
3445+ auto pos21 = realType.find (' ,' ); // for the case there are more template args
3446+ auto pos22 = realType.find (' >' );
3447+ auto len = std::min (pos21 - pos1, pos22 - pos1) - 1 ;
3448+ std::string value_type = realType.substr (pos1+1 , len);
3449+
3450+ // strip leading "const "
3451+ const std::string cprefix = " const " ;
3452+ if (value_type.compare (0 , cprefix.size (), cprefix) == 0 ) {
3453+ value_type = value_type.substr (cprefix.size ());
3454+ }
3455+
3456+ std::string span_type = " std::span<" + value_type + " >" ;
3457+
3458+ return new StdSpanConverter{value_type, Cppyy::GetScope (span_type)};
3459+ }
3460+ #endif
3461+
33343462// converters for known C++ classes and default (void*)
33353463 Converter* result = nullptr ;
33363464 if (Cppyy::TCppScope_t klass = Cppyy::GetFullScope (realType)) {
@@ -3372,7 +3500,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim
33723500 if (h != gConvFactories .end ())
33733501 return (h->second )(dims);
33743502 // else, unhandled moves
3375- result = new NotImplementedConverter (failure_msg) ;
3503+ result = new NotImplementedConverter{PyExc_NotImplementedError, " this method cannot (yet) be called " } ;
33763504 }
33773505
33783506 if (!result && h != gConvFactories .end ())
@@ -3385,7 +3513,8 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim
33853513 else if (!cpd.empty ())
33863514 result = new VoidArrayConverter (/* keepControl= */ true , failure_msg); // "user knows best"
33873515 else
3388- result = new NotImplementedConverter (failure_msg); // fails on use
3516+ // fails on use
3517+ result = new NotImplementedConverter{PyExc_NotImplementedError, " this method cannot (yet) be called" };
33893518 }
33903519
33913520 return result;
@@ -3429,6 +3558,14 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t d
34293558 std::string realTypeStr = Cppyy::GetTypeAsString (realType);
34303559 std::string realUnresolvedTypeStr = TypeManip::clean_type (fullType, false , true );
34313560
3561+ // mutable pointer references (T*&) are incompatible with Python's object model
3562+ if (cpd == " *&" ) {
3563+ return new NotImplementedConverter{PyExc_TypeError,
3564+ " argument type '" + resolvedTypeStr + " ' is not supported: non-const references to pointers (T*&) allow a"
3565+ " function to replace the pointer itself. Python cannot represent this safely. Consider changing the"
3566+ " C++ API to return the new pointer or use a wrapper" };
3567+ }
3568+
34323569// accept unqualified type (as python does not know about qualifiers)
34333570 h = gConvFactories .find ((isConst ? " const " : " " ) + realTypeStr + cpd);
34343571 if (h != gConvFactories .end ()) {
@@ -3579,7 +3716,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t d
35793716 if (h != gConvFactories .end ())
35803717 return (h->second )(dims);
35813718 // else, unhandled moves
3582- result = new NotImplementedConverter (failure_msg) ;
3719+ result = new NotImplementedConverter{PyExc_NotImplementedError, " this method cannot (yet) be called " } ;
35833720 }
35843721
35853722 if (!result && h != gConvFactories .end ()) {
@@ -3592,7 +3729,8 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t d
35923729 } else if (!cpd.empty ()) {
35933730 result = new VoidArrayConverter (/* keepControl= */ true , failure_msg); // "user knows best"
35943731 } else {
3595- result = new NotImplementedConverter (failure_msg); // fails on use
3732+ // fails on use
3733+ result = new NotImplementedConverter{PyExc_NotImplementedError, " this method cannot (yet) be called" };
35963734 }
35973735 }
35983736
0 commit comments