##// END OF EJS Templates
rust-pyo3: exposition of AncestorsIterator...
Georges Racinet -
r53428:4c9e3198 default
parent child Browse files
Show More
@@ -0,0 +1,87
1 // ancestors.rs
2 //
3 // Copyright 2024 Georges Racinet <georges.racinet@cloudcrane.io>
4 //
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
7
8 //! Bindings for the `hg::ancestors` module provided by the
9 //! `hg-core` crate. From Python, this will be seen as `pyo3_rustext.ancestor`
10 //! and can be used as replacement for the the pure `ancestor` Python module.
11 use cpython::UnsafePyLeaked;
12 use pyo3::prelude::*;
13
14 use std::sync::RwLock;
15
16 use vcsgraph::lazy_ancestors::AncestorsIterator as VCGAncestorsIterator;
17
18 use crate::convert_cpython::{
19 proxy_index_extract, proxy_index_py_leak, py_leaked_borrow_mut,
20 py_leaked_or_map_err,
21 };
22 use crate::exceptions::{map_lock_error, GraphError};
23 use crate::revision::{rev_pyiter_collect, PyRevision};
24 use crate::util::new_submodule;
25 use rusthg::revlog::PySharedIndex;
26
27 #[pyclass]
28 struct AncestorsIterator {
29 inner: RwLock<UnsafePyLeaked<VCGAncestorsIterator<PySharedIndex>>>,
30 }
31
32 #[pymethods]
33 impl AncestorsIterator {
34 #[new]
35 fn new(
36 index_proxy: &Bound<'_, PyAny>,
37 initrevs: &Bound<'_, PyAny>,
38 stoprev: PyRevision,
39 inclusive: bool,
40 ) -> PyResult<Self> {
41 // Safety: we don't leak the "faked" reference out of
42 // `UnsafePyLeaked`
43 let initvec: Vec<_> = {
44 let borrowed_idx = unsafe { proxy_index_extract(index_proxy)? };
45 rev_pyiter_collect(initrevs, borrowed_idx)?
46 };
47 let (py, leaked_idx) = proxy_index_py_leak(index_proxy)?;
48 let res_ait = unsafe {
49 leaked_idx.map(py, |idx| {
50 VCGAncestorsIterator::new(
51 idx,
52 initvec.into_iter().map(|r| r.0),
53 stoprev.0,
54 inclusive,
55 )
56 })
57 };
58 let ait =
59 py_leaked_or_map_err(py, res_ait, GraphError::from_vcsgraph)?;
60 let inner = ait.into();
61 Ok(Self { inner })
62 }
63
64 fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
65 slf
66 }
67
68 fn __next__(slf: PyRefMut<'_, Self>) -> PyResult<Option<PyRevision>> {
69 let mut leaked = slf.inner.write().map_err(map_lock_error)?;
70 // Safety: we don't leak the inner 'static ref out of UnsafePyLeaked
71 let mut inner = unsafe { py_leaked_borrow_mut(&slf, &mut leaked)? };
72 match inner.next() {
73 Some(Err(e)) => Err(GraphError::from_vcsgraph(e)),
74 None => Ok(None),
75 Some(Ok(r)) => Ok(Some(PyRevision(r))),
76 }
77 }
78 }
79
80 pub fn init_module<'py>(
81 py: Python<'py>,
82 package: &str,
83 ) -> PyResult<Bound<'py, PyModule>> {
84 let m = new_submodule(py, package, "ancestor")?;
85 m.add_class::<AncestorsIterator>()?;
86 Ok(m)
87 }
@@ -1,286 +1,285
1 //! This module takes care of all conversions involving `rusthg` (hg-cpython)
1 //! This module takes care of all conversions involving `rusthg` (hg-cpython)
2 //! objects in the PyO3 call context.
2 //! objects in the PyO3 call context.
3 //!
3 //!
4 //! For source code clarity, we only import (`use`) [`cpython`] traits and not
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
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
6 //! `cpython::PyObject`, and believe that the added heaviness is an acceptatble
7 //! price to pay to avoid confusion.
7 //! price to pay to avoid confusion.
8 //!
8 //!
9 //! Also it, is customary in [`cpython`] to label the GIL lifetime as `'p`,
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
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.
11 //! the arguments side of function signatures when they are not simply elided.
12 use pyo3::exceptions::PyTypeError;
12 use pyo3::exceptions::PyTypeError;
13 use pyo3::prelude::*;
13 use pyo3::prelude::*;
14 use pyo3::{pyclass::boolean_struct::False, PyClass};
14 use pyo3::{pyclass::boolean_struct::False, PyClass};
15
15
16 use cpython::ObjectProtocol;
16 use cpython::ObjectProtocol;
17 use cpython::PythonObject;
17 use cpython::PythonObject;
18 use lazy_static::lazy_static;
18 use lazy_static::lazy_static;
19
19
20 use hg::revlog::index::Index as CoreIndex;
20 use hg::revlog::index::Index as CoreIndex;
21 use rusthg::revlog::{InnerRevlog, PySharedIndex};
21 use rusthg::revlog::{InnerRevlog, PySharedIndex};
22
22
23 /// Marker trait for PyO3 objects with a lifetime representing the acquired GIL
23 /// Marker trait for PyO3 objects with a lifetime representing the acquired GIL
24 ///
24 ///
25 /// # Safety
25 /// # Safety
26 ///
26 ///
27 /// This trait must not be implemented for objects with lifetimes that
27 /// This trait must not be implemented for objects with lifetimes that
28 /// do not imply in PyO3 that the GIL is acquired during the whole lifetime.
28 /// do not imply in PyO3 that the GIL is acquired during the whole lifetime.
29 pub unsafe trait WithGIL<'py> {}
29 pub unsafe trait WithGIL<'py> {}
30
30
31 // Safety: the lifetime on these PyO3 objects all represent the acquired GIL
31 // Safety: the lifetime on these PyO3 objects all represent the acquired GIL
32 unsafe impl<'py> WithGIL<'py> for Python<'py> {}
32 unsafe impl<'py> WithGIL<'py> for Python<'py> {}
33 unsafe impl<'py, T> WithGIL<'py> for Bound<'py, T> {}
33 unsafe impl<'py, T> WithGIL<'py> for Bound<'py, T> {}
34 unsafe impl<'py, T: PyClass> WithGIL<'py> for PyRef<'py, T> {}
34 unsafe impl<'py, T: PyClass> WithGIL<'py> for PyRef<'py, T> {}
35 unsafe impl<'py, T: PyClass<Frozen = False>> WithGIL<'py>
35 unsafe impl<'py, T: PyClass<Frozen = False>> WithGIL<'py>
36 for PyRefMut<'py, T>
36 for PyRefMut<'py, T>
37 {
37 {
38 }
38 }
39
39
40 /// Force cpython's GIL handle with the appropriate lifetime
40 /// Force cpython's GIL handle with the appropriate lifetime
41 ///
41 ///
42 /// In `pyo3`, the fact that we have the GIL is expressed by the lifetime of
42 /// In `pyo3`, the fact that we have the GIL is expressed by the lifetime of
43 /// the incoming [`Bound`] smart pointer. We therefore simply instantiate
43 /// the incoming [`Bound`] smart pointer. We therefore simply instantiate
44 /// the `cpython` handle and coerce its lifetime by the function signature.
44 /// the `cpython` handle and coerce its lifetime by the function signature.
45 ///
45 ///
46 /// Reacquiring the GIL is also a possible alternative, as the CPython
46 /// Reacquiring the GIL is also a possible alternative, as the CPython
47 /// documentation explicitely states that "recursive calls are allowed"
47 /// documentation explicitely states that "recursive calls are allowed"
48 /// (we interpret that as saying that acquiring the GIL within a thread that
48 /// (we interpret that as saying that acquiring the GIL within a thread that
49 /// already has it works) *as long as it is properly released*
49 /// already has it works) *as long as it is properly released*
50 /// reference:
50 /// reference:
51 /// <https://docs.python.org/3.8/c-api/init.html#c.PyGILState_Ensure>
51 /// <https://docs.python.org/3.8/c-api/init.html#c.PyGILState_Ensure>
52 pub(crate) fn cpython_handle<'py, T: WithGIL<'py>>(
52 pub(crate) fn cpython_handle<'py, T: WithGIL<'py>>(
53 _with_gil: &T,
53 _with_gil: &T,
54 ) -> cpython::Python<'py> {
54 ) -> cpython::Python<'py> {
55 // safety: this is safe because the returned object has the same lifetime
55 // safety: this is safe because the returned object has the same lifetime
56 // as the incoming object.
56 // as the incoming object.
57 unsafe { cpython::Python::assume_gil_acquired() }
57 unsafe { cpython::Python::assume_gil_acquired() }
58 }
58 }
59
59
60 /// Force PyO3 GIL handle from cpython's.
60 /// Force PyO3 GIL handle from cpython's.
61 ///
61 ///
62 /// Very similar to [`cpython_handle`]
62 /// Very similar to [`cpython_handle`]
63 pub fn pyo3_handle(_py: cpython::Python<'_>) -> Python<'_> {
63 pub fn pyo3_handle(_py: cpython::Python<'_>) -> Python<'_> {
64 // safety: this is safe because the returned object has the same lifetime
64 // safety: this is safe because the returned object has the same lifetime
65 // as the incoming object.
65 // as the incoming object.
66 unsafe { Python::assume_gil_acquired() }
66 unsafe { Python::assume_gil_acquired() }
67 }
67 }
68
68
69 /// Convert a PyO3 [`PyObject`] into a [`cpython::PyObject`]
69 /// Convert a PyO3 [`PyObject`] into a [`cpython::PyObject`]
70 ///
70 ///
71 /// During this process, the reference count is increased, then decreased.
71 /// During this process, the reference count is increased, then decreased.
72 /// This means that the GIL (symbolized by the lifetime on the `obj`
72 /// This means that the GIL (symbolized by the lifetime on the `obj`
73 /// argument) is needed.
73 /// argument) is needed.
74 ///
74 ///
75 /// We could make something perhaps more handy by simply stealing the
75 /// We could make something perhaps more handy by simply stealing the
76 /// pointer, forgetting the incoming and then implement `From` with "newtype".
76 /// pointer, forgetting the incoming and then implement `From` with "newtype".
77 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
77 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
78 /// not for the current endeavour.
78 /// not for the current endeavour.
79 pub(crate) fn to_cpython_py_object<'py>(
79 pub(crate) fn to_cpython_py_object<'py>(
80 obj: &Bound<'py, PyAny>,
80 obj: &Bound<'py, PyAny>,
81 ) -> (cpython::Python<'py>, cpython::PyObject) {
81 ) -> (cpython::Python<'py>, cpython::PyObject) {
82 let py = cpython_handle(obj);
82 let py = cpython_handle(obj);
83 // public alias of the private cpython::fii::PyObject (!)
83 // public alias of the private cpython::fii::PyObject (!)
84 let raw = obj.as_ptr() as *mut python3_sys::PyObject;
84 let raw = obj.as_ptr() as *mut python3_sys::PyObject;
85 // both pyo3 and rust-cpython will decrement the refcount on drop.
85 // both pyo3 and rust-cpython will decrement the refcount on drop.
86 // If we use from_owned_ptr, that's a segfault.
86 // If we use from_owned_ptr, that's a segfault.
87 (py, unsafe { cpython::PyObject::from_borrowed_ptr(py, raw) })
87 (py, unsafe { cpython::PyObject::from_borrowed_ptr(py, raw) })
88 }
88 }
89
89
90 /// Convert a [`cpython::PyObject`] into a PyO3 [`PyObject`]
90 /// Convert a [`cpython::PyObject`] into a PyO3 [`PyObject`]
91 ///
91 ///
92 /// During this process, the reference count is increased, then decreased.
92 /// During this process, the reference count is increased, then decreased.
93 /// This means that the GIL (symbolized by the PyO3 [`Python`] handle is
93 /// This means that the GIL (symbolized by the PyO3 [`Python`] handle is
94 /// needed.
94 /// needed.
95 ///
95 ///
96 /// We could make something perhaps more handy by simply stealing the
96 /// We could make something perhaps more handy by simply stealing the
97 /// pointer, forgetting the incoming and then implement `From` with "newtype".
97 /// pointer, forgetting the incoming and then implement `From` with "newtype".
98 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
98 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
99 /// not for the current endeavour.
99 /// not for the current endeavour.
100 pub(crate) fn from_cpython_py_object(
100 pub(crate) fn from_cpython_py_object(
101 py: Python<'_>,
101 py: Python<'_>,
102 obj: cpython::PyObject,
102 obj: cpython::PyObject,
103 ) -> PyObject {
103 ) -> PyObject {
104 let raw = obj.as_ptr() as *mut pyo3::ffi::PyObject;
104 let raw = obj.as_ptr() as *mut pyo3::ffi::PyObject;
105 unsafe { Py::from_borrowed_ptr(py, raw) }
105 unsafe { Py::from_borrowed_ptr(py, raw) }
106 }
106 }
107
107
108 /// Convert [`cpython::PyErr`] into [`pyo3::PyErr`]
108 /// Convert [`cpython::PyErr`] into [`pyo3::PyErr`]
109 ///
109 ///
110 /// The exception class remains the same as the original exception,
110 /// The exception class remains the same as the original exception,
111 /// hence if it is also defined in another dylib based on `cpython` crate,
111 /// hence if it is also defined in another dylib based on `cpython` crate,
112 /// it will need to be converted to be downcasted in this crate.
112 /// it will need to be converted to be downcasted in this crate.
113 pub(crate) fn from_cpython_pyerr(
113 pub(crate) fn from_cpython_pyerr(
114 py: cpython::Python<'_>,
114 py: cpython::Python<'_>,
115 mut e: cpython::PyErr,
115 mut e: cpython::PyErr,
116 ) -> PyErr {
116 ) -> PyErr {
117 let pyo3_py = pyo3_handle(py);
117 let pyo3_py = pyo3_handle(py);
118 let cpython_exc_obj = e.instance(py);
118 let cpython_exc_obj = e.instance(py);
119 let pyo3_exc_obj = from_cpython_py_object(pyo3_py, cpython_exc_obj);
119 let pyo3_exc_obj = from_cpython_py_object(pyo3_py, cpython_exc_obj);
120 PyErr::from_value(pyo3_exc_obj.into_bound(pyo3_py))
120 PyErr::from_value(pyo3_exc_obj.into_bound(pyo3_py))
121 }
121 }
122
122
123 /// Retrieve the PyType for objects from the `mercurial.rustext` crate.
123 /// Retrieve the PyType for objects from the `mercurial.rustext` crate.
124 fn retrieve_cpython_py_type(
124 fn retrieve_cpython_py_type(
125 submodule_name: &str,
125 submodule_name: &str,
126 type_name: &str,
126 type_name: &str,
127 ) -> cpython::PyResult<cpython::PyType> {
127 ) -> cpython::PyResult<cpython::PyType> {
128 let guard = cpython::Python::acquire_gil();
128 let guard = cpython::Python::acquire_gil();
129 let py = guard.python();
129 let py = guard.python();
130 let module = py.import(&format!("mercurial.rustext.{submodule_name}"))?;
130 let module = py.import(&format!("mercurial.rustext.{submodule_name}"))?;
131 module.get(py, type_name)?.extract::<cpython::PyType>(py)
131 module.get(py, type_name)?.extract::<cpython::PyType>(py)
132 }
132 }
133
133
134 lazy_static! {
134 lazy_static! {
135 static ref INNER_REVLOG_PY_TYPE: cpython::PyType = {
135 static ref INNER_REVLOG_PY_TYPE: cpython::PyType = {
136 retrieve_cpython_py_type("revlog", "InnerRevlog")
136 retrieve_cpython_py_type("revlog", "InnerRevlog")
137 .expect("Could not import InnerRevlog in Python")
137 .expect("Could not import InnerRevlog in Python")
138 };
138 };
139 }
139 }
140
140
141 /// Downcast [`InnerRevlog`], with the appropriate Python type checking.
141 /// Downcast [`InnerRevlog`], with the appropriate Python type checking.
142 ///
142 ///
143 /// The PyType object representing the `InnerRevlog` Python class is not the
143 /// The PyType object representing the `InnerRevlog` Python class is not the
144 /// the same in this dylib as it is in the `mercurial.rustext` module.
144 /// the same in this dylib as it is in the `mercurial.rustext` module.
145 /// This is because the code created with the [`cpython::py_class!`]
145 /// This is because the code created with the [`cpython::py_class!`]
146 /// macro is itself duplicated in both dylibs. In the case of this crate, this
146 /// macro is itself duplicated in both dylibs. In the case of this crate, this
147 /// happens by linking to the [`rusthg`] crate and provides the `InnerRevlog`
147 /// happens by linking to the [`rusthg`] crate and provides the `InnerRevlog`
148 /// that is visible from this crate. The `InnerRevlog::get_type` associated
148 /// that is visible from this crate. The `InnerRevlog::get_type` associated
149 /// function turns out to return a `static mut` (look for `TYPE_OBJECT` in
149 /// function turns out to return a `static mut` (look for `TYPE_OBJECT` in
150 /// `py_class_impl3.rs`), which obviously is different in both dylibs.
150 /// `py_class_impl3.rs`), which obviously is different in both dylibs.
151 ///
151 ///
152 /// The consequence of that is that downcasting an `InnerRevlog` originally
152 /// The consequence of that is that downcasting an `InnerRevlog` originally
153 /// from the `mecurial.rustext` module to our `InnerRevlog` cannot be done with
153 /// from the `mecurial.rustext` module to our `InnerRevlog` cannot be done with
154 /// the usual `extract::<InnerRevlog>(py)`, as it would perform the type
154 /// the usual `extract::<InnerRevlog>(py)`, as it would perform the type
155 /// checking with the `PyType` that is embedded in `mercurial.pyo3_rustext`.
155 /// checking with the `PyType` that is embedded in `mercurial.pyo3_rustext`.
156 /// We must check the `PyType` that is within `mercurial.rustext` instead.
156 /// We must check the `PyType` that is within `mercurial.rustext` instead.
157 /// This is what this function does.
157 /// This is what this function does.
158 fn extract_inner_revlog(
158 fn extract_inner_revlog(
159 py: cpython::Python,
159 py: cpython::Python,
160 inner_revlog: cpython::PyObject,
160 inner_revlog: cpython::PyObject,
161 ) -> PyResult<InnerRevlog> {
161 ) -> PyResult<InnerRevlog> {
162 if !(*INNER_REVLOG_PY_TYPE).is_instance(py, &inner_revlog) {
162 if !(*INNER_REVLOG_PY_TYPE).is_instance(py, &inner_revlog) {
163 return Err(PyTypeError::new_err("Not an InnerRevlog instance"));
163 return Err(PyTypeError::new_err("Not an InnerRevlog instance"));
164 }
164 }
165 // Safety: this is safe because we checked the PyType already, with the
165 // Safety: this is safe because we checked the PyType already, with the
166 // value embedded in `mercurial.rustext`.
166 // value embedded in `mercurial.rustext`.
167 Ok(unsafe { InnerRevlog::unchecked_downcast_from(inner_revlog) })
167 Ok(unsafe { InnerRevlog::unchecked_downcast_from(inner_revlog) })
168 }
168 }
169
169
170 /// This is similar to [`rusthg.py_rust_index_to_graph`], with difference in
170 /// This is similar to [`rusthg.py_rust_index_to_graph`], with difference in
171 /// how we retrieve the [`InnerRevlog`].
171 /// how we retrieve the [`InnerRevlog`].
172 pub fn py_rust_index_to_graph(
172 pub fn py_rust_index_to_graph(
173 py: cpython::Python,
173 py: cpython::Python,
174 index_proxy: cpython::PyObject,
174 index_proxy: cpython::PyObject,
175 ) -> PyResult<cpython::UnsafePyLeaked<PySharedIndex>> {
175 ) -> PyResult<cpython::UnsafePyLeaked<PySharedIndex>> {
176 let inner_revlog = extract_inner_revlog(
176 let inner_revlog = extract_inner_revlog(
177 py,
177 py,
178 index_proxy
178 index_proxy
179 .getattr(py, "inner")
179 .getattr(py, "inner")
180 .map_err(|e| from_cpython_pyerr(py, e))?,
180 .map_err(|e| from_cpython_pyerr(py, e))?,
181 )?;
181 )?;
182
182
183 let leaked = inner_revlog.pub_inner(py).leak_immutable();
183 let leaked = inner_revlog.pub_inner(py).leak_immutable();
184 // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked`
184 // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked`
185 Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) })
185 Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) })
186 }
186 }
187
187
188 pub(crate) fn proxy_index_py_leak<'py>(
188 pub(crate) fn proxy_index_py_leak<'py>(
189 index_proxy: &Bound<'py, PyAny>,
189 index_proxy: &Bound<'py, PyAny>,
190 ) -> PyResult<(cpython::Python<'py>, cpython::UnsafePyLeaked<PySharedIndex>)> {
190 ) -> PyResult<(cpython::Python<'py>, cpython::UnsafePyLeaked<PySharedIndex>)> {
191 let (py, idx_proxy) = to_cpython_py_object(index_proxy);
191 let (py, idx_proxy) = to_cpython_py_object(index_proxy);
192 let py_leaked = py_rust_index_to_graph(py, idx_proxy)?;
192 let py_leaked = py_rust_index_to_graph(py, idx_proxy)?;
193 Ok((py, py_leaked))
193 Ok((py, py_leaked))
194 }
194 }
195
195
196 /// Full extraction of the proxy index object as received in PyO3 to a
196 /// Full extraction of the proxy index object as received in PyO3 to a
197 /// [`CoreIndex`] reference.
197 /// [`CoreIndex`] reference.
198 ///
198 ///
199 /// # Safety
199 /// # Safety
200 ///
200 ///
201 /// The invariants to maintain are those of the underlying
201 /// The invariants to maintain are those of the underlying
202 /// [`UnsafePyLeaked::try_borrow`]: the caller must not leak the inner
202 /// [`UnsafePyLeaked::try_borrow`]: the caller must not leak the inner
203 /// reference.
203 /// reference.
204 pub(crate) unsafe fn proxy_index_extract<'py>(
204 pub(crate) unsafe fn proxy_index_extract<'py>(
205 index_proxy: &Bound<'py, PyAny>,
205 index_proxy: &Bound<'py, PyAny>,
206 ) -> PyResult<&'py CoreIndex> {
206 ) -> PyResult<&'py CoreIndex> {
207 let (py, py_leaked) = proxy_index_py_leak(index_proxy)?;
207 let (py, py_leaked) = proxy_index_py_leak(index_proxy)?;
208 let py_shared = &*unsafe {
208 let py_shared = &*unsafe {
209 py_leaked
209 py_leaked
210 .try_borrow(py)
210 .try_borrow(py)
211 .map_err(|e| from_cpython_pyerr(py, e))?
211 .map_err(|e| from_cpython_pyerr(py, e))?
212 };
212 };
213 Ok(py_shared.inner)
213 Ok(py_shared.inner)
214 }
214 }
215
215
216 /// Generic borrow of [`cpython::UnsafePyLeaked`], with proper mapping.
216 /// Generic borrow of [`cpython::UnsafePyLeaked`], with proper mapping.
217 ///
217 ///
218 /// # Safety
218 /// # Safety
219 ///
219 ///
220 /// The invariants to maintain are those of the underlying
220 /// The invariants to maintain are those of the underlying
221 /// [`UnsafePyLeaked::try_borrow`]: the caller must not leak the inner
221 /// [`UnsafePyLeaked::try_borrow`]: the caller must not leak the inner
222 /// static reference. It is possible, depending on `T` that such a leak cannot
222 /// static reference. It is possible, depending on `T` that such a leak cannot
223 /// occur in practice. We may later on define a marker trait for this,
223 /// occur in practice. We may later on define a marker trait for this,
224 /// which will allow us to make declare this function to be safe.
224 /// which will allow us to make declare this function to be safe.
225 #[allow(dead_code)]
225 #[allow(dead_code)]
226 pub(crate) unsafe fn py_leaked_borrow<'a, 'py: 'a, T>(
226 pub(crate) unsafe fn py_leaked_borrow<'a, 'py: 'a, T>(
227 py: &impl WithGIL<'py>,
227 py: &impl WithGIL<'py>,
228 leaked: &'a cpython::UnsafePyLeaked<T>,
228 leaked: &'a cpython::UnsafePyLeaked<T>,
229 ) -> PyResult<cpython::PyLeakedRef<'a, T>> {
229 ) -> PyResult<cpython::PyLeakedRef<'a, T>> {
230 let py = cpython_handle(py);
230 let py = cpython_handle(py);
231 leaked.try_borrow(py).map_err(|e| from_cpython_pyerr(py, e))
231 leaked.try_borrow(py).map_err(|e| from_cpython_pyerr(py, e))
232 }
232 }
233
233
234 /// Mutable variant of [`py_leaked_borrow`]
234 /// Mutable variant of [`py_leaked_borrow`]
235 ///
235 ///
236 /// # Safety
236 /// # Safety
237 ///
237 ///
238 /// See [`py_leaked_borrow`]
238 /// See [`py_leaked_borrow`]
239 #[allow(dead_code)]
239 #[allow(dead_code)]
240 pub(crate) unsafe fn py_leaked_borrow_mut<'a, 'py: 'a, T>(
240 pub(crate) unsafe fn py_leaked_borrow_mut<'a, 'py: 'a, T>(
241 py: &impl WithGIL<'py>,
241 py: &impl WithGIL<'py>,
242 leaked: &'a mut cpython::UnsafePyLeaked<T>,
242 leaked: &'a mut cpython::UnsafePyLeaked<T>,
243 ) -> PyResult<cpython::PyLeakedRefMut<'a, T>> {
243 ) -> PyResult<cpython::PyLeakedRefMut<'a, T>> {
244 let py = cpython_handle(py);
244 let py = cpython_handle(py);
245 leaked
245 leaked
246 .try_borrow_mut(py)
246 .try_borrow_mut(py)
247 .map_err(|e| from_cpython_pyerr(py, e))
247 .map_err(|e| from_cpython_pyerr(py, e))
248 }
248 }
249
249
250 /// Error propagation for an [`UnsafePyLeaked`] wrapping a [`Result`]
250 /// Error propagation for an [`UnsafePyLeaked`] wrapping a [`Result`]
251 ///
251 ///
252 /// TODO (will consider when implementing UnsafePyLeaked in PyO3):
252 /// TODO (will consider when implementing UnsafePyLeaked in PyO3):
253 /// It would be nice for UnsafePyLeaked to provide this directly as a variant
253 /// It would be nice for UnsafePyLeaked to provide this directly as a variant
254 /// of the `map` method with a signature such as:
254 /// of the `map` method with a signature such as:
255 ///
255 ///
256 /// ```
256 /// ```
257 /// unsafe fn map_or_err(&self,
257 /// unsafe fn map_or_err(&self,
258 /// py: Python,
258 /// py: Python,
259 /// f: impl FnOnce(T) -> Result(U, E),
259 /// f: impl FnOnce(T) -> Result(U, E),
260 /// convert_err: impl FnOnce(E) -> PyErr)
260 /// convert_err: impl FnOnce(E) -> PyErr)
261 /// ```
261 /// ```
262 ///
262 ///
263 /// This would spare users of the `cpython` crate the additional `unsafe` deref
263 /// This would spare users of the `cpython` crate the additional `unsafe` deref
264 /// to inspect the error and return it outside `UnsafePyLeaked`, and the
264 /// to inspect the error and return it outside `UnsafePyLeaked`, and the
265 /// subsequent unwrapping that this function performs.
265 /// subsequent unwrapping that this function performs.
266 #[allow(dead_code)]
267 pub(crate) fn py_leaked_or_map_err<T, E: std::fmt::Debug + Copy>(
266 pub(crate) fn py_leaked_or_map_err<T, E: std::fmt::Debug + Copy>(
268 py: cpython::Python,
267 py: cpython::Python,
269 leaked: cpython::UnsafePyLeaked<Result<T, E>>,
268 leaked: cpython::UnsafePyLeaked<Result<T, E>>,
270 convert_err: impl FnOnce(E) -> PyErr,
269 convert_err: impl FnOnce(E) -> PyErr,
271 ) -> PyResult<cpython::UnsafePyLeaked<T>> {
270 ) -> PyResult<cpython::UnsafePyLeaked<T>> {
272 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
271 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
273 if let Err(e) = *unsafe {
272 if let Err(e) = *unsafe {
274 leaked
273 leaked
275 .try_borrow(py)
274 .try_borrow(py)
276 .map_err(|e| from_cpython_pyerr(py, e))?
275 .map_err(|e| from_cpython_pyerr(py, e))?
277 } {
276 } {
278 return Err(convert_err(e));
277 return Err(convert_err(e));
279 }
278 }
280 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
279 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
281 Ok(unsafe {
280 Ok(unsafe {
282 leaked.map(py, |res| {
281 leaked.map(py, |res| {
283 res.expect("Error case should have already be treated")
282 res.expect("Error case should have already be treated")
284 })
283 })
285 })
284 })
286 }
285 }
@@ -1,34 +1,38
1 use pyo3::exceptions::PyValueError;
1 use pyo3::exceptions::{PyRuntimeError, PyValueError};
2 use pyo3::import_exception;
2 use pyo3::import_exception;
3 use pyo3::{create_exception, PyErr};
3 use pyo3::{create_exception, PyErr};
4
4
5 use crate::revision::PyRevision;
5 use crate::revision::PyRevision;
6
6
7 create_exception!(pyo3_rustext, GraphError, PyValueError);
7 create_exception!(pyo3_rustext, GraphError, PyValueError);
8 import_exception!(mercurial.error, WdirUnsupported);
8 import_exception!(mercurial.error, WdirUnsupported);
9
9
10 impl GraphError {
10 impl GraphError {
11 pub fn from_hg(inner: hg::GraphError) -> PyErr {
11 pub fn from_hg(inner: hg::GraphError) -> PyErr {
12 match inner {
12 match inner {
13 hg::GraphError::ParentOutOfRange(r) => {
13 hg::GraphError::ParentOutOfRange(r) => {
14 GraphError::new_err(("ParentOutOfRange", PyRevision(r.0)))
14 GraphError::new_err(("ParentOutOfRange", PyRevision(r.0)))
15 }
15 }
16 }
16 }
17 }
17 }
18 pub fn from_vcsgraph(inner: vcsgraph::graph::GraphReadError) -> PyErr {
18 pub fn from_vcsgraph(inner: vcsgraph::graph::GraphReadError) -> PyErr {
19 match inner {
19 match inner {
20 vcsgraph::graph::GraphReadError::InconsistentGraphData => {
20 vcsgraph::graph::GraphReadError::InconsistentGraphData => {
21 GraphError::new_err("InconsistentGraphData")
21 GraphError::new_err("InconsistentGraphData")
22 }
22 }
23 vcsgraph::graph::GraphReadError::InvalidKey => {
23 vcsgraph::graph::GraphReadError::InvalidKey => {
24 GraphError::new_err("ParentOutOfRange")
24 GraphError::new_err("ParentOutOfRange")
25 }
25 }
26 vcsgraph::graph::GraphReadError::KeyedInvalidKey(r) => {
26 vcsgraph::graph::GraphReadError::KeyedInvalidKey(r) => {
27 GraphError::new_err(("ParentOutOfRange", r))
27 GraphError::new_err(("ParentOutOfRange", r))
28 }
28 }
29 vcsgraph::graph::GraphReadError::WorkingDirectoryUnsupported => {
29 vcsgraph::graph::GraphReadError::WorkingDirectoryUnsupported => {
30 WdirUnsupported::new_err(())
30 WdirUnsupported::new_err(())
31 }
31 }
32 }
32 }
33 }
33 }
34 }
34 }
35
36 pub fn map_lock_error<T>(e: std::sync::PoisonError<T>) -> PyErr {
37 PyRuntimeError::new_err(format!("In Rust PyO3 bindings: {e}"))
38 }
@@ -1,23 +1,25
1 use pyo3::prelude::*;
1 use pyo3::prelude::*;
2
2
3 mod ancestors;
3 mod convert_cpython;
4 mod convert_cpython;
4 mod dagops;
5 mod dagops;
5 mod exceptions;
6 mod exceptions;
6 mod revision;
7 mod revision;
7 mod util;
8 mod util;
8
9
9 #[pymodule]
10 #[pymodule]
10 fn pyo3_rustext(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
11 fn pyo3_rustext(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
11 m.add(
12 m.add(
12 "__doc__",
13 "__doc__",
13 "Mercurial core concepts - Rust implementation exposed via PyO3",
14 "Mercurial core concepts - Rust implementation exposed via PyO3",
14 )?;
15 )?;
15 // the module's __name__ is pyo3_rustext, not mercurial.pyo3_rustext
16 // the module's __name__ is pyo3_rustext, not mercurial.pyo3_rustext
16 // (at least at this point).
17 // (at least at this point).
17 let name: String = m.getattr("__name__")?.extract()?;
18 let name: String = m.getattr("__name__")?.extract()?;
18 let dotted_name = format!("mercurial.{}", name);
19 let dotted_name = format!("mercurial.{}", name);
19
20
21 m.add_submodule(&ancestors::init_module(py, &dotted_name)?)?;
20 m.add_submodule(&dagops::init_module(py, &dotted_name)?)?;
22 m.add_submodule(&dagops::init_module(py, &dotted_name)?)?;
21 m.add("GraphError", py.get_type::<exceptions::GraphError>())?;
23 m.add("GraphError", py.get_type::<exceptions::GraphError>())?;
22 Ok(())
24 Ok(())
23 }
25 }
General Comments 0
You need to be logged in to leave comments. Login now