Add additional info to TypeError when C++->Python casting fails (#3605)

* Add additional info to TypeInfo when C++->Python casting fails

* Fix typo

* Address reviewer comments
This commit is contained in:
Aaron Gokaslan 2022-01-10 21:18:00 -05:00 committed by GitHub
parent b66328b043
commit ef070f7750
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 5 deletions

View File

@ -996,6 +996,13 @@ protected:
"Python type! The signature was\n\t"; "Python type! The signature was\n\t";
msg += it->signature; msg += it->signature;
append_note_if_missing_header_is_suspected(msg); append_note_if_missing_header_is_suspected(msg);
#if PY_VERSION_HEX >= 0x03030000
// Attach additional error info to the exception if supported
if (PyErr_Occurred()) {
raise_from(PyExc_TypeError, msg.c_str());
return nullptr;
}
#endif
PyErr_SetString(PyExc_TypeError, msg.c_str()); PyErr_SetString(PyExc_TypeError, msg.c_str());
return nullptr; return nullptr;
} }

View File

@ -7,10 +7,11 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include "pybind11_tests.h"
#include "constructor_stats.h" #include "constructor_stats.h"
#include <pybind11/operators.h> #include "pybind11_tests.h"
#include <functional> #include <functional>
#include <pybind11/operators.h>
#include <pybind11/stl.h>
class Vector2 { class Vector2 {
public: public:
@ -71,6 +72,12 @@ int operator+(const C2 &, const C2 &) { return 22; }
int operator+(const C2 &, const C1 &) { return 21; } int operator+(const C2 &, const C1 &) { return 21; }
int operator+(const C1 &, const C2 &) { return 12; } int operator+(const C1 &, const C2 &) { return 12; }
struct HashMe {
std::string member;
};
bool operator==(const HashMe &lhs, const HashMe &rhs) { return lhs.member == rhs.member; }
// Note: Specializing explicit within `namespace std { ... }` is done due to a // Note: Specializing explicit within `namespace std { ... }` is done due to a
// bug in GCC<7. If you are supporting compilers later than this, consider // bug in GCC<7. If you are supporting compilers later than this, consider
// specializing `using template<> struct std::hash<...>` in the global // specializing `using template<> struct std::hash<...>` in the global
@ -82,6 +89,14 @@ namespace std {
// Not a good hash function, but easy to test // Not a good hash function, but easy to test
size_t operator()(const Vector2 &) { return 4; } size_t operator()(const Vector2 &) { return 4; }
}; };
// HashMe has a hash function in C++ but no `__hash__` for Python.
template <>
struct hash<HashMe> {
std::size_t operator()(const HashMe &selector) const {
return std::hash<std::string>()(selector.member);
}
};
} // namespace std } // namespace std
// Not a good abs function, but easy to test. // Not a good abs function, but easy to test.
@ -228,8 +243,12 @@ TEST_SUBMODULE(operators, m) {
.def("__hash__", &Hashable::hash) .def("__hash__", &Hashable::hash)
.def(py::init<int>()) .def(py::init<int>())
.def(py::self == py::self); .def(py::self == py::self);
}
// define __eq__ but not __hash__
py::class_<HashMe>(m, "HashMe").def(py::self == py::self);
m.def("get_unhashable_HashMe_set", []() { return std::unordered_set<HashMe>{{"one"}}; });
}
#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER) #if !defined(_MSC_VER) && !defined(__INTEL_COMPILER)
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#endif #endif

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pytest import pytest
import env
from pybind11_tests import ConstructorStats from pybind11_tests import ConstructorStats
from pybind11_tests import operators as m from pybind11_tests import operators as m
@ -135,8 +136,9 @@ def test_overriding_eq_reset_hash():
assert m.Comparable(15) is not m.Comparable(15) assert m.Comparable(15) is not m.Comparable(15)
assert m.Comparable(15) == m.Comparable(15) assert m.Comparable(15) == m.Comparable(15)
with pytest.raises(TypeError): with pytest.raises(TypeError) as excinfo:
hash(m.Comparable(15)) # TypeError: unhashable type: 'm.Comparable' hash(m.Comparable(15))
assert str(excinfo.value).startswith("unhashable type:")
for hashable in (m.Hashable, m.Hashable2): for hashable in (m.Hashable, m.Hashable2):
assert hashable(15) is not hashable(15) assert hashable(15) is not hashable(15)
@ -144,3 +146,10 @@ def test_overriding_eq_reset_hash():
assert hash(hashable(15)) == 15 assert hash(hashable(15)) == 15
assert hash(hashable(15)) == hash(hashable(15)) assert hash(hashable(15)) == hash(hashable(15))
def test_return_set_of_unhashable():
with pytest.raises(TypeError) as excinfo:
m.get_unhashable_HashMe_set()
if not env.PY2:
assert str(excinfo.value.__cause__).startswith("unhashable type:")