Skip to content

Commit ef070f7

Browse files
authored
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
1 parent b66328b commit ef070f7

File tree

3 files changed

+40
-5
lines changed

3 files changed

+40
-5
lines changed

include/pybind11/pybind11.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,13 @@ class cpp_function : public function {
996996
"Python type! The signature was\n\t";
997997
msg += it->signature;
998998
append_note_if_missing_header_is_suspected(msg);
999+
#if PY_VERSION_HEX >= 0x03030000
1000+
// Attach additional error info to the exception if supported
1001+
if (PyErr_Occurred()) {
1002+
raise_from(PyExc_TypeError, msg.c_str());
1003+
return nullptr;
1004+
}
1005+
#endif
9991006
PyErr_SetString(PyExc_TypeError, msg.c_str());
10001007
return nullptr;
10011008
}

tests/test_operator_overloading.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
BSD-style license that can be found in the LICENSE file.
88
*/
99

10-
#include "pybind11_tests.h"
1110
#include "constructor_stats.h"
12-
#include <pybind11/operators.h>
11+
#include "pybind11_tests.h"
1312
#include <functional>
13+
#include <pybind11/operators.h>
14+
#include <pybind11/stl.h>
1415

1516
class Vector2 {
1617
public:
@@ -71,6 +72,12 @@ int operator+(const C2 &, const C2 &) { return 22; }
7172
int operator+(const C2 &, const C1 &) { return 21; }
7273
int operator+(const C1 &, const C2 &) { return 12; }
7374

75+
struct HashMe {
76+
std::string member;
77+
};
78+
79+
bool operator==(const HashMe &lhs, const HashMe &rhs) { return lhs.member == rhs.member; }
80+
7481
// Note: Specializing explicit within `namespace std { ... }` is done due to a
7582
// bug in GCC<7. If you are supporting compilers later than this, consider
7683
// specializing `using template<> struct std::hash<...>` in the global
@@ -82,6 +89,14 @@ namespace std {
8289
// Not a good hash function, but easy to test
8390
size_t operator()(const Vector2 &) { return 4; }
8491
};
92+
93+
// HashMe has a hash function in C++ but no `__hash__` for Python.
94+
template <>
95+
struct hash<HashMe> {
96+
std::size_t operator()(const HashMe &selector) const {
97+
return std::hash<std::string>()(selector.member);
98+
}
99+
};
85100
} // namespace std
86101

87102
// Not a good abs function, but easy to test.
@@ -228,8 +243,12 @@ TEST_SUBMODULE(operators, m) {
228243
.def("__hash__", &Hashable::hash)
229244
.def(py::init<int>())
230245
.def(py::self == py::self);
231-
}
232246

247+
// define __eq__ but not __hash__
248+
py::class_<HashMe>(m, "HashMe").def(py::self == py::self);
249+
250+
m.def("get_unhashable_HashMe_set", []() { return std::unordered_set<HashMe>{{"one"}}; });
251+
}
233252
#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER)
234253
#pragma GCC diagnostic pop
235254
#endif

tests/test_operator_overloading.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
import pytest
33

4+
import env
45
from pybind11_tests import ConstructorStats
56
from pybind11_tests import operators as m
67

@@ -135,12 +136,20 @@ def test_overriding_eq_reset_hash():
135136
assert m.Comparable(15) is not m.Comparable(15)
136137
assert m.Comparable(15) == m.Comparable(15)
137138

138-
with pytest.raises(TypeError):
139-
hash(m.Comparable(15)) # TypeError: unhashable type: 'm.Comparable'
139+
with pytest.raises(TypeError) as excinfo:
140+
hash(m.Comparable(15))
141+
assert str(excinfo.value).startswith("unhashable type:")
140142

141143
for hashable in (m.Hashable, m.Hashable2):
142144
assert hashable(15) is not hashable(15)
143145
assert hashable(15) == hashable(15)
144146

145147
assert hash(hashable(15)) == 15
146148
assert hash(hashable(15)) == hash(hashable(15))
149+
150+
151+
def test_return_set_of_unhashable():
152+
with pytest.raises(TypeError) as excinfo:
153+
m.get_unhashable_HashMe_set()
154+
if not env.PY2:
155+
assert str(excinfo.value.__cause__).startswith("unhashable type:")

0 commit comments

Comments
 (0)