File: regina.patch

package info (click to toggle)
regina-normal 7.4.1-1.1
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 154,244 kB
  • sloc: cpp: 295,026; xml: 9,992; sh: 1,344; python: 1,225; perl: 616; ansic: 138; makefile: 26
file content (218 lines) | stat: -rw-r--r-- 10,700 bytes parent folder | download
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
diff --git a/python/pybind11_v3/pybind11/functional.h b/python/pybind11_v3/pybind11/functional.h
index 8f59f5fe5..5a554abad 100644
--- a/python/pybind11_v3/pybind11/functional.h
+++ b/python/pybind11_v3/pybind11/functional.h
@@ -29,12 +29,18 @@ struct func_handle {
     }
     func_handle(const func_handle &f_) { operator=(f_); }
     func_handle &operator=(const func_handle &f_) {
-        gil_scoped_acquire acq;
+        // Called on the way into a Regina function that expects
+        // a std::function argument.  In such situations the
+        // interpreter should already be holding the GIL.
+        // gil_scoped_acquire acq;
         f = f_.f;
         return *this;
     }
     ~func_handle() {
-        gil_scoped_acquire acq;
+        // Called on the way into and also out of a Regina function
+        // that expects a std::function argument.  In such situations
+        // the interpreter should already be holding the GIL.
+        // gil_scoped_acquire acq;
         function kill_f(std::move(f));
     }
 };
@@ -49,7 +55,10 @@ template <typename Return, typename... Args>
 struct func_wrapper : func_wrapper_base {
     using func_wrapper_base::func_wrapper_base;
     Return operator()(Args... args) const { // NOLINT(performance-unnecessary-value-param)
-        gil_scoped_acquire acq;
+        // Called when a std::function is executed as a callback within
+        // one of Regina's own functions.  In such situations Regina's
+        // Python bindings are responsible for ensuring the GIL is held.
+        // gil_scoped_acquire acq;
         // casts the returned object as a rvalue to the return type
         return hfunc.f(std::forward<Args>(args)...).template cast<Return>();
     }
diff --git a/python/pybind11_v3/pybind11/gil.h b/python/pybind11_v3/pybind11/gil.h
index 4222a035f..fef69b641 100644
--- a/python/pybind11_v3/pybind11/gil.h
+++ b/python/pybind11_v3/pybind11/gil.h
@@ -197,3 +197,30 @@ private:
 PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
 
 #endif // !PYBIND11_SIMPLE_GIL_MANAGEMENT
+
+PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+
+/**
+ * A less lightweight version of gil_scoped_acquire that actually checks
+ * whether we are already holding the GIL before trying to acquire it.
+ *
+ * This is needed (for example) in PacketListener callbacks in the GUI, since
+ * we do not know whether listener methods are reacting to some action in the
+ * C++ GUI (where the GIL will not be held) or in a Python console (where the
+ * GIL will be held).
+ *
+ * - Ben Burton, 25/06/2025.
+ */
+class safe_gil_scoped_acquire {
+    std::optional<gil_scoped_acquire> gil;
+public:
+    safe_gil_scoped_acquire() {
+        if (! PyGILState_Check())
+            gil.emplace();
+    }
+    safe_gil_scoped_acquire(const safe_gil_scoped_acquire&) = delete;
+    safe_gil_scoped_acquire& operator = (const safe_gil_scoped_acquire&) =
+        delete;
+};
+
+PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff --git a/python/pybind11_v3/pybind11/pybind11.h b/python/pybind11_v3/pybind11/pybind11.h
index 06be7f1d4..d20de7ecc 100644
--- a/python/pybind11_v3/pybind11/pybind11.h
+++ b/python/pybind11_v3/pybind11/pybind11.h
@@ -33,6 +33,10 @@
 #include <utility>
 #include <vector>
 
+// This allows us to verify that we are #including our own patched pybind11,
+// not some other pybind11 that has been installed system-wide.
+#define REGINA_PYBIND11 1
+
 // See PR #5448. This warning suppression is needed for the PYBIND11_OVERRIDE macro family.
 // NOTE that this is NOT embedded in a push/pop pair because that is very difficult to achieve.
 #if defined(__clang_major__) && __clang_major__ < 14
@@ -43,6 +47,10 @@ PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
 #    include <cxxabi.h>
 #endif
 
+namespace regina {
+    const char* pythonTypename(const std::type_info*);
+}
+
 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
 
 /* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning
@@ -101,6 +109,16 @@ inline std::string replace_newlines_and_squash(const char *text) {
     return result.substr(str_begin, str_range);
 }
 
+/* Regina-specific tweak to generate_function_signature */
+inline std::string fix_regina(const std::string& modulename, const std::string& qualname) {
+    if (modulename == "regina.engine")
+        return "regina." + qualname;
+    else if (modulename == "regina.engine.internal")
+        return "regina.internal." + qualname;
+    else
+        return modulename + "." + qualname;
+}
+
 /* Generate a proper function signature */
 inline std::string generate_function_signature(const char *type_caster_name_field,
                                                detail::function_record *func_rec,
@@ -157,18 +175,43 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel
             if (!t) {
                 pybind11_fail("Internal error while parsing type signature (1)");
             }
-            if (auto *tinfo = detail::get_type_info(*t)) {
+            if (const char* name = regina::pythonTypename(t)) {
+                signature += name;
+            } else if (auto *tinfo = detail::get_type_info(*t)) {
                 handle th((PyObject *) tinfo->type);
-                signature += th.attr("__module__").cast<std::string>() + "."
-                             + th.attr("__qualname__").cast<std::string>();
+                std::string qualname;
+                try {
+                    qualname = th.attr("__qualname__").cast<std::string>();
+                } catch (...) {
+                    // In Python 3.12 we get an error in our stripped-down
+                    // interpreter since PythonOutputStream is missing both
+                    // __qualname__ and __name__.  Do not make this fatal.
+                    qualname = "<unknown>";
+                }
+                const auto m = th.attr("__module__").cast<std::string>();
+                signature += fix_regina(m, qualname);
             } else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) {
-                signature += th.attr("__module__").cast<std::string>() + "."
-                             + th.attr("__qualname__").cast<std::string>();
+                std::string qualname;
+                try {
+                    qualname = th.attr("__qualname__").cast<std::string>();
+                } catch (...) {
+                    // See above for the explanation here.
+                    qualname = "<unknown>";
+                }
+                const auto m = th.attr("__module__").cast<std::string>();
+                signature += fix_regina(m, qualname);
             } else if (func_rec->is_new_style_constructor && arg_index == 0) {
                 // A new-style `__init__` takes `self` as `value_and_holder`.
                 // Rewrite it to the proper class type.
-                signature += func_rec->scope.attr("__module__").cast<std::string>() + "."
-                             + func_rec->scope.attr("__qualname__").cast<std::string>();
+                std::string qualname;
+                try {
+                    qualname = func_rec->scope.attr("__qualname__").cast<std::string>();
+                } catch (...) {
+                    // See above for the explanation here.
+                    qualname = "<unknown>";
+                }
+                const auto m = func_rec->scope.attr("__module__").cast<std::string>();
+                signature += fix_regina(m, qualname);
             } else {
                 signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name()));
             }
@@ -1110,6 +1153,16 @@ protected:
         } catch (abi::__forced_unwind &) {
             throw;
 #endif
+        } catch (const pybind11::stop_iteration& stop) {
+            /* We prioritise catching stop_iteration before any of the other
+               exception logic below, since stop_iteration is arguably the one
+               setting where exceptions are normal as opposed to "exceptional".
+               The default exception logic involves many try/catch/rethrow
+               blocks, and in settings where try/catch is slow (e.g., running
+               within SageMath on macOS), this can add to a noticeable
+               performance penalty when using iterators. */
+            PyErr_SetString(PyExc_StopIteration, stop.what());
+            return nullptr;
         } catch (...) {
             try_translate_exceptions();
             return nullptr;
@@ -3213,10 +3266,10 @@ template <typename type>
 class exception : public object {
 public:
     exception() = default;
-    exception(handle scope, const char *name, handle base = PyExc_Exception) {
+    exception(handle scope, const char *name, const char* doc, handle base) {
         std::string full_name
             = scope.attr("__name__").cast<std::string>() + std::string(".") + name;
-        m_ptr = PyErr_NewException(const_cast<char *>(full_name.c_str()), base.ptr(), nullptr);
+        m_ptr = PyErr_NewExceptionWithDoc(const_cast<char *>(full_name.c_str()), doc, base.ptr(), nullptr);
         if (hasattr(scope, "__dict__") && scope.attr("__dict__").contains(name)) {
             pybind11_fail("Error during initialization: multiple incompatible "
                           "definitions with name \""
@@ -3521,7 +3521,7 @@ function get_override(const T *this_ptr, const char *name) {
 
 #define PYBIND11_OVERRIDE_IMPL(ret_type, cname, name, ...)                                        \
     do {                                                                                          \
-        pybind11::gil_scoped_acquire gil;                                                         \
+        pybind11::safe_gil_scoped_acquire gil;                                                    \
         pybind11::function override                                                               \
             = pybind11::get_override(static_cast<const cname *>(this), name);                     \
         if (override) {                                                                           \
diff --git a/python/pybind11_v3/pybind11/subinterpreter.h b/python/pybind11_v3/pybind11/subinterpreter.h
index 9f2f704c5..cb666cf28 100644
--- a/python/pybind11_v3/pybind11/subinterpreter.h
+++ b/python/pybind11_v3/pybind11/subinterpreter.h
@@ -21,7 +21,7 @@
 
 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
 PYBIND11_NAMESPACE_BEGIN(detail)
-PyInterpreterState *get_interpreter_state_unchecked() {
+inline PyInterpreterState *get_interpreter_state_unchecked() {
     auto cur_tstate = get_thread_state_unchecked();
     if (cur_tstate)
         return cur_tstate->interp;