mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 13:15:12 +00:00
fix: only allow integer type_caster to call __int__ method when conversion is allowed; always call __index__ (#2698)
* Only allow integer type_caster to call __int__ or __index__ method when conversion is allowed * Remove tests for __index__ as this seems to only be used to convert to int in 3.8+ * Take both `int` and `long` types into account for Python 2 * Add test_numpy_int_convert to assert tests currently fail, even though np.intc has an __index__ method * Also consider __index__ as noconvert to a C++ integer * New-style classes for Python 2.7; sigh * Add some tests on types with custom __index__ method * Ignore some tests in Python <3.8 * Update comment about conversion from np.float32 to C++ int * Workaround difference between CPython and PyPy's different PyIndex_Check (unnoticed because we currently don't have PyPy >= 3.8) * Avoid ICC segfault with py::arg()
This commit is contained in:
parent
0df11d857c
commit
8449a8089c
@ -1025,6 +1025,14 @@ public:
|
||||
if (!src)
|
||||
return false;
|
||||
|
||||
#if !defined(PYPY_VERSION)
|
||||
auto index_check = [](PyObject *o) { return PyIndex_Check(o); };
|
||||
#else
|
||||
// In PyPy 7.3.3, `PyIndex_Check` is implemented by calling `__index__`,
|
||||
// while CPython only considers the existence of `nb_index`/`__index__`.
|
||||
auto index_check = [](PyObject *o) { return hasattr(o, "__index__"); };
|
||||
#endif
|
||||
|
||||
if (std::is_floating_point<T>::value) {
|
||||
if (convert || PyFloat_Check(src.ptr()))
|
||||
py_value = (py_type) PyFloat_AsDouble(src.ptr());
|
||||
@ -1032,6 +1040,8 @@ public:
|
||||
return false;
|
||||
} else if (PyFloat_Check(src.ptr())) {
|
||||
return false;
|
||||
} else if (!convert && !index_check(src.ptr()) && !PYBIND11_LONG_CHECK(src.ptr())) {
|
||||
return false;
|
||||
} else if (std::is_unsigned<py_type>::value) {
|
||||
py_value = as_unsigned<py_type>(src.ptr());
|
||||
} else { // signed integer:
|
||||
|
@ -141,6 +141,10 @@ TEST_SUBMODULE(builtin_casters, m) {
|
||||
m.def("i64_str", [](std::int64_t v) { return std::to_string(v); });
|
||||
m.def("u64_str", [](std::uint64_t v) { return std::to_string(v); });
|
||||
|
||||
// test_int_convert
|
||||
m.def("int_passthrough", [](int arg) { return arg; });
|
||||
m.def("int_passthrough_noconvert", [](int arg) { return arg; }, py::arg{}.noconvert());
|
||||
|
||||
// test_tuple
|
||||
m.def("pair_passthrough", [](std::pair<bool, std::string> input) {
|
||||
return std::make_pair(input.second, input.first);
|
||||
|
@ -251,6 +251,68 @@ def test_integer_casting():
|
||||
assert "incompatible function arguments" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_int_convert():
|
||||
class DeepThought(object):
|
||||
def __int__(self):
|
||||
return 42
|
||||
|
||||
class ShallowThought(object):
|
||||
pass
|
||||
|
||||
class FuzzyThought(object):
|
||||
def __float__(self):
|
||||
return 41.99999
|
||||
|
||||
class IndexedThought(object):
|
||||
def __index__(self):
|
||||
return 42
|
||||
|
||||
class RaisingThought(object):
|
||||
def __index__(self):
|
||||
raise ValueError
|
||||
|
||||
def __int__(self):
|
||||
return 42
|
||||
|
||||
convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert
|
||||
|
||||
def require_implicit(v):
|
||||
pytest.raises(TypeError, noconvert, v)
|
||||
|
||||
def cant_convert(v):
|
||||
pytest.raises(TypeError, convert, v)
|
||||
|
||||
assert convert(7) == 7
|
||||
assert noconvert(7) == 7
|
||||
cant_convert(3.14159)
|
||||
assert convert(DeepThought()) == 42
|
||||
require_implicit(DeepThought())
|
||||
cant_convert(ShallowThought())
|
||||
cant_convert(FuzzyThought())
|
||||
if env.PY >= (3, 8):
|
||||
# Before Python 3.8, `int(obj)` does not pick up on `obj.__index__`
|
||||
assert convert(IndexedThought()) == 42
|
||||
assert noconvert(IndexedThought()) == 42
|
||||
cant_convert(RaisingThought()) # no fall-back to `__int__`if `__index__` raises
|
||||
|
||||
|
||||
def test_numpy_int_convert():
|
||||
np = pytest.importorskip("numpy")
|
||||
|
||||
convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert
|
||||
|
||||
def require_implicit(v):
|
||||
pytest.raises(TypeError, noconvert, v)
|
||||
|
||||
# `np.intc` is an alias that corresponds to a C++ `int`
|
||||
assert convert(np.intc(42)) == 42
|
||||
assert noconvert(np.intc(42)) == 42
|
||||
|
||||
# The implicit conversion from np.float32 is undesirable but currently accepted.
|
||||
assert convert(np.float32(3.14159)) == 3
|
||||
require_implicit(np.float32(3.14159))
|
||||
|
||||
|
||||
def test_tuple(doc):
|
||||
"""std::pair <-> tuple & std::tuple <-> tuple"""
|
||||
assert m.pair_passthrough((True, "test")) == ("test", True)
|
||||
|
Loading…
Reference in New Issue
Block a user