-
Notifications
You must be signed in to change notification settings - Fork 153
/
nb_error.h
152 lines (128 loc) · 5.08 KB
/
nb_error.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
nanobind/nb_error.h: Python exception handling, binding of exceptions
Copyright (c) 2022 Wenzel Jakob
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
NAMESPACE_BEGIN(NB_NAMESPACE)
/// RAII wrapper that temporarily clears any Python error state
#if PY_VERSION_HEX >= 0x030C0000
struct error_scope {
error_scope() { value = PyErr_GetRaisedException(); }
~error_scope() { PyErr_SetRaisedException(value); }
private:
PyObject *value;
};
#else
struct error_scope {
error_scope() { PyErr_Fetch(&type, &value, &trace); }
~error_scope() { PyErr_Restore(type, value, trace); }
private:
PyObject *type, *value, *trace;
};
#endif
/// Wraps a Python error state as a C++ exception
class NB_EXPORT python_error : public std::exception {
public:
NB_EXPORT_SHARED python_error();
NB_EXPORT_SHARED python_error(const python_error &);
NB_EXPORT_SHARED python_error(python_error &&) noexcept;
NB_EXPORT_SHARED ~python_error() override;
bool matches(handle exc) const noexcept {
#if PY_VERSION_HEX < 0x030C0000
return PyErr_GivenExceptionMatches(m_type, exc.ptr()) != 0;
#else
return PyErr_GivenExceptionMatches(m_value, exc.ptr()) != 0;
#endif
}
/// Move the error back into the Python domain. This may only be called
/// once, and you should not reraise the exception in C++ afterward.
NB_EXPORT_SHARED void restore() noexcept;
/// Pass the error to Python's `sys.unraisablehook`, which prints
/// a traceback to `sys.stderr` by default but may be overridden.
/// The *context* should be some object whose repr() helps clarify where
/// the error occurred. Like `.restore()`, this consumes the error and
/// you should not reraise the exception in C++ afterward.
void discard_as_unraisable(handle context) noexcept {
restore();
PyErr_WriteUnraisable(context.ptr());
}
void discard_as_unraisable(const char *context) noexcept {
object context_s = steal(PyUnicode_FromString(context));
discard_as_unraisable(context_s);
}
handle value() const { return m_value; }
#if PY_VERSION_HEX < 0x030C0000
handle type() const { return m_type; }
object traceback() const { return borrow(m_traceback); }
#else
handle type() const { return value().type(); }
object traceback() const { return steal(PyException_GetTraceback(m_value)); }
#endif
[[deprecated]]
object trace() const { return traceback(); }
NB_EXPORT_SHARED const char *what() const noexcept override;
private:
#if PY_VERSION_HEX < 0x030C0000
mutable PyObject *m_type = nullptr;
mutable PyObject *m_value = nullptr;
mutable PyObject *m_traceback = nullptr;
#else
mutable PyObject *m_value = nullptr;
#endif
mutable char *m_what = nullptr;
};
/// Thrown by nanobind::cast when casting fails
using cast_error = std::bad_cast;
enum class exception_type {
runtime_error, stop_iteration, index_error, key_error, value_error,
type_error, buffer_error, import_error, attribute_error, next_overload
};
// Base interface used to expose common Python exceptions in C++
class NB_EXPORT builtin_exception : public std::runtime_error {
public:
NB_EXPORT_SHARED builtin_exception(exception_type type, const char *what);
NB_EXPORT_SHARED builtin_exception(builtin_exception &&) = default;
NB_EXPORT_SHARED builtin_exception(const builtin_exception &) = default;
NB_EXPORT_SHARED ~builtin_exception();
NB_EXPORT_SHARED exception_type type() const { return m_type; }
private:
exception_type m_type;
};
#define NB_EXCEPTION(name) \
inline builtin_exception name(const char *what = nullptr) { \
return builtin_exception(exception_type::name, what); \
}
NB_EXCEPTION(stop_iteration)
NB_EXCEPTION(index_error)
NB_EXCEPTION(key_error)
NB_EXCEPTION(value_error)
NB_EXCEPTION(type_error)
NB_EXCEPTION(buffer_error)
NB_EXCEPTION(import_error)
NB_EXCEPTION(attribute_error)
NB_EXCEPTION(next_overload)
#undef NB_EXCEPTION
inline void register_exception_translator(detail::exception_translator t,
void *payload = nullptr) {
detail::register_exception_translator(t, payload);
}
template <typename T>
class exception : public object {
NB_OBJECT_DEFAULT(exception, object, "Exception", PyExceptionClass_Check)
exception(handle scope, const char *name, handle base = PyExc_Exception)
: object(detail::exception_new(scope.ptr(), name, base.ptr()),
detail::steal_t()) {
detail::register_exception_translator(
[](const std::exception_ptr &p, void *payload) {
try {
std::rethrow_exception(p);
} catch (T &e) {
PyErr_SetString((PyObject *) payload, e.what());
}
}, m_ptr);
}
};
NB_CORE void chain_error(handle type, const char *fmt, ...) noexcept;
[[noreturn]] NB_CORE void raise_from(python_error &e, handle type, const char *fmt, ...);
NAMESPACE_END(NB_NAMESPACE)