|
1 //! This module takes care of all conversions involving `rusthg` (hg-cpython) |
|
2 //! objects in the PyO3 call context. |
|
3 //! |
|
4 //! For source code clarity, we only import (`use`) [`cpython`] traits and not |
|
5 //! any of its data objects. We are instead using full qualifiers, such as |
|
6 //! `cpython::PyObject`, and believe that the added heaviness is an acceptatble |
|
7 //! price to pay to avoid confusion. |
|
8 //! |
|
9 //! Also it, is customary in [`cpython`] to label the GIL lifetime as `'p`, |
|
10 //! whereas it is `'py` in PyO3 context. We keep both these conventions in |
|
11 //! the arguments side of function signatures when they are not simply elided. |
|
12 use pyo3::exceptions::PyTypeError; |
|
13 use pyo3::prelude::*; |
|
14 |
|
15 use cpython::ObjectProtocol; |
|
16 use cpython::PythonObject; |
|
17 use lazy_static::lazy_static; |
|
18 |
|
19 use rusthg::revlog::{InnerRevlog, PySharedIndex}; |
|
20 |
|
21 /// Force cpython's GIL handle with the appropriate lifetime |
|
22 /// |
|
23 /// In `pyo3`, the fact that we have the GIL is expressed by the lifetime of |
|
24 /// the incoming [`Bound`] smart pointer. We therefore simply instantiate |
|
25 /// the `cpython` handle and coerce its lifetime by the function signature. |
|
26 /// |
|
27 /// Reacquiring the GIL is also a possible alternative, as the CPython |
|
28 /// documentation explicitely states that "recursive calls are allowed" |
|
29 /// (we interpret that as saying that acquiring the GIL within a thread that |
|
30 /// already has it works) *as long as it is properly released* |
|
31 /// reference: |
|
32 /// <https://docs.python.org/3.8/c-api/init.html#c.PyGILState_Ensure> |
|
33 pub(crate) fn cpython_handle<'py, T>( |
|
34 _bound: &Bound<'py, T>, |
|
35 ) -> cpython::Python<'py> { |
|
36 // safety: this is safe because the returned object has the 'py lifetime |
|
37 unsafe { cpython::Python::assume_gil_acquired() } |
|
38 } |
|
39 |
|
40 /// Force PyO3 GIL handle from cpython's. |
|
41 /// |
|
42 /// Very similar to [`cpython_handle`] |
|
43 pub fn pyo3_handle(_py: cpython::Python<'_>) -> Python<'_> { |
|
44 // safety: this is safe because the returned object has the same lifetime |
|
45 // as the incoming object. |
|
46 unsafe { Python::assume_gil_acquired() } |
|
47 } |
|
48 |
|
49 /// Convert a PyO3 [`PyObject`] into a [`cpython::PyObject`] |
|
50 /// |
|
51 /// During this process, the reference count is increased, then decreased. |
|
52 /// This means that the GIL (symbolized by the lifetime on the `obj` |
|
53 /// argument) is needed. |
|
54 /// |
|
55 /// We could make something perhaps more handy by simply stealing the |
|
56 /// pointer, forgetting the incoming and then implement `From` with "newtype". |
|
57 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps |
|
58 /// not for the current endeavour. |
|
59 pub(crate) fn to_cpython_py_object<'py>( |
|
60 obj: &Bound<'py, PyAny>, |
|
61 ) -> (cpython::Python<'py>, cpython::PyObject) { |
|
62 let py = cpython_handle(obj); |
|
63 // public alias of the private cpython::fii::PyObject (!) |
|
64 let raw = obj.as_ptr() as *mut python3_sys::PyObject; |
|
65 // both pyo3 and rust-cpython will decrement the refcount on drop. |
|
66 // If we use from_owned_ptr, that's a segfault. |
|
67 (py, unsafe { cpython::PyObject::from_borrowed_ptr(py, raw) }) |
|
68 } |
|
69 |
|
70 /// Convert a [`cpython::PyObject`] into a PyO3 [`PyObject`] |
|
71 /// |
|
72 /// During this process, the reference count is increased, then decreased. |
|
73 /// This means that the GIL (symbolized by the PyO3 [`Python`] handle is |
|
74 /// needed. |
|
75 /// |
|
76 /// We could make something perhaps more handy by simply stealing the |
|
77 /// pointer, forgetting the incoming and then implement `From` with "newtype". |
|
78 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps |
|
79 /// not for the current endeavour. |
|
80 pub(crate) fn from_cpython_py_object( |
|
81 py: Python<'_>, |
|
82 obj: cpython::PyObject, |
|
83 ) -> PyObject { |
|
84 let raw = obj.as_ptr() as *mut pyo3::ffi::PyObject; |
|
85 unsafe { Py::from_borrowed_ptr(py, raw) } |
|
86 } |
|
87 |
|
88 /// Convert [`cpython::PyErr`] into [`pyo3::PyErr`] |
|
89 /// |
|
90 /// The exception class remains the same as the original exception, |
|
91 /// hence if it is also defined in another dylib based on `cpython` crate, |
|
92 /// it will need to be converted to be downcasted in this crate. |
|
93 pub(crate) fn from_cpython_pyerr( |
|
94 py: cpython::Python<'_>, |
|
95 mut e: cpython::PyErr, |
|
96 ) -> PyErr { |
|
97 let pyo3_py = pyo3_handle(py); |
|
98 let cpython_exc_obj = e.instance(py); |
|
99 let pyo3_exc_obj = from_cpython_py_object(pyo3_py, cpython_exc_obj); |
|
100 PyErr::from_value(pyo3_exc_obj.into_bound(pyo3_py)) |
|
101 } |
|
102 |
|
103 /// Retrieve the PyType for objects from the `mercurial.rustext` crate. |
|
104 fn retrieve_cpython_py_type( |
|
105 submodule_name: &str, |
|
106 type_name: &str, |
|
107 ) -> cpython::PyResult<cpython::PyType> { |
|
108 let guard = cpython::Python::acquire_gil(); |
|
109 let py = guard.python(); |
|
110 let module = py.import(&format!("mercurial.rustext.{submodule_name}"))?; |
|
111 module.get(py, type_name)?.extract::<cpython::PyType>(py) |
|
112 } |
|
113 |
|
114 lazy_static! { |
|
115 static ref INNER_REVLOG_PY_TYPE: cpython::PyType = { |
|
116 retrieve_cpython_py_type("revlog", "InnerRevlog") |
|
117 .expect("Could not import InnerRevlog in Python") |
|
118 }; |
|
119 } |
|
120 |
|
121 /// Downcast [`InnerRevlog`], with the appropriate Python type checking. |
|
122 /// |
|
123 /// The PyType object representing the `InnerRevlog` Python class is not the |
|
124 /// the same in this dylib as it is in the `mercurial.rustext` module. |
|
125 /// This is because the code created with the [`cpython::py_class!`] |
|
126 /// macro is itself duplicated in both dylibs. In the case of this crate, this |
|
127 /// happens by linking to the [`rusthg`] crate and provides the `InnerRevlog` |
|
128 /// that is visible from this crate. The `InnerRevlog::get_type` associated |
|
129 /// function turns out to return a `static mut` (look for `TYPE_OBJECT` in |
|
130 /// `py_class_impl3.rs`), which obviously is different in both dylibs. |
|
131 /// |
|
132 /// The consequence of that is that downcasting an `InnerRevlog` originally |
|
133 /// from the `mecurial.rustext` module to our `InnerRevlog` cannot be done with |
|
134 /// the usual `extract::<InnerRevlog>(py)`, as it would perform the type |
|
135 /// checking with the `PyType` that is embedded in `mercurial.pyo3_rustext`. |
|
136 /// We must check the `PyType` that is within `mercurial.rustext` instead. |
|
137 /// This is what this function does. |
|
138 fn extract_inner_revlog( |
|
139 py: cpython::Python, |
|
140 inner_revlog: cpython::PyObject, |
|
141 ) -> PyResult<InnerRevlog> { |
|
142 if !(*INNER_REVLOG_PY_TYPE).is_instance(py, &inner_revlog) { |
|
143 return Err(PyTypeError::new_err("Not an InnerRevlog instance")); |
|
144 } |
|
145 // Safety: this is safe because we checked the PyType already, with the |
|
146 // value embedded in `mercurial.rustext`. |
|
147 Ok(unsafe { InnerRevlog::unchecked_downcast_from(inner_revlog) }) |
|
148 } |
|
149 |
|
150 /// This is similar to [`rusthg.py_rust_index_to_graph`], with difference in |
|
151 /// how we retrieve the [`InnerRevlog`]. |
|
152 pub fn py_rust_index_to_graph( |
|
153 py: cpython::Python, |
|
154 index_proxy: cpython::PyObject, |
|
155 ) -> PyResult<cpython::UnsafePyLeaked<PySharedIndex>> { |
|
156 let inner_revlog = extract_inner_revlog( |
|
157 py, |
|
158 index_proxy |
|
159 .getattr(py, "inner") |
|
160 .map_err(|e| from_cpython_pyerr(py, e))?, |
|
161 )?; |
|
162 |
|
163 let leaked = inner_revlog.pub_inner(py).leak_immutable(); |
|
164 // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked` |
|
165 Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) }) |
|
166 } |
|
167 |
|
168 pub(crate) fn proxy_index_extract<'py>( |
|
169 index_proxy: &Bound<'py, PyAny>, |
|
170 ) -> PyResult<(cpython::Python<'py>, cpython::UnsafePyLeaked<PySharedIndex>)> { |
|
171 let (py, idx_proxy) = to_cpython_py_object(index_proxy); |
|
172 Ok((py, py_rust_index_to_graph(py, idx_proxy)?)) |
|
173 } |