aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYannick Jadoul <yannick.jadoul@belgacom.net>2021-01-17 02:52:14 +0100
committerGitHub <noreply@github.com>2021-01-16 20:52:14 -0500
commit8449a8089c312e7138865e0af93b8d6ea971f83e (patch)
tree32842b57595cd0fadfbf42df019effdaea67c938
parent0df11d857ce21a1f85aad1d18116cd4959f13f1d (diff)
downloadpybind11-8449a8089c312e7138865e0af93b8d6ea971f83e.tar.gz
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()
-rw-r--r--include/pybind11/cast.h10
-rw-r--r--tests/test_builtin_casters.cpp4
-rw-r--r--tests/test_builtin_casters.py62
3 files changed, 76 insertions, 0 deletions
diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h
index d2869264..ce85d997 100644
--- a/include/pybind11/cast.h
+++ b/include/pybind11/cast.h
@@ -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:
diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp
index 79144594..16a78b1a 100644
--- a/tests/test_builtin_casters.cpp
+++ b/tests/test_builtin_casters.cpp
@@ -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);
diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py
index 39e8711d..0a469842 100644
--- a/tests/test_builtin_casters.py
+++ b/tests/test_builtin_casters.py
@@ -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)