Show More
@@ -0,0 +1,375 b'' | |||||
|
1 | // macros.rs | |||
|
2 | // | |||
|
3 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | |||
|
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 | //! Macros for use in the `hg-cpython` bridge library. | |||
|
9 | ||||
|
10 | use crate::exceptions::AlreadyBorrowed; | |||
|
11 | use cpython::{PyResult, Python}; | |||
|
12 | use std::cell::{Cell, RefCell, RefMut}; | |||
|
13 | ||||
|
14 | /// Manages the shared state between Python and Rust | |||
|
15 | #[derive(Default)] | |||
|
16 | pub struct PySharedState { | |||
|
17 | leak_count: Cell<usize>, | |||
|
18 | mutably_borrowed: Cell<bool>, | |||
|
19 | } | |||
|
20 | ||||
|
21 | impl PySharedState { | |||
|
22 | pub fn borrow_mut<'a, T>( | |||
|
23 | &'a self, | |||
|
24 | py: Python<'a>, | |||
|
25 | pyrefmut: RefMut<'a, T>, | |||
|
26 | ) -> PyResult<PyRefMut<'a, T>> { | |||
|
27 | if self.mutably_borrowed.get() { | |||
|
28 | return Err(AlreadyBorrowed::new( | |||
|
29 | py, | |||
|
30 | "Cannot borrow mutably while there exists another \ | |||
|
31 | mutable reference in a Python object", | |||
|
32 | )); | |||
|
33 | } | |||
|
34 | match self.leak_count.get() { | |||
|
35 | 0 => { | |||
|
36 | self.mutably_borrowed.replace(true); | |||
|
37 | Ok(PyRefMut::new(py, pyrefmut, self)) | |||
|
38 | } | |||
|
39 | // TODO | |||
|
40 | // For now, this works differently than Python references | |||
|
41 | // in the case of iterators. | |||
|
42 | // Python does not complain when the data an iterator | |||
|
43 | // points to is modified if the iterator is never used | |||
|
44 | // afterwards. | |||
|
45 | // Here, we are stricter than this by refusing to give a | |||
|
46 | // mutable reference if it is already borrowed. | |||
|
47 | // While the additional safety might be argued for, it | |||
|
48 | // breaks valid programming patterns in Python and we need | |||
|
49 | // to fix this issue down the line. | |||
|
50 | _ => Err(AlreadyBorrowed::new( | |||
|
51 | py, | |||
|
52 | "Cannot borrow mutably while there are \ | |||
|
53 | immutable references in Python objects", | |||
|
54 | )), | |||
|
55 | } | |||
|
56 | } | |||
|
57 | ||||
|
58 | /// Return a reference to the wrapped data with an artificial static | |||
|
59 | /// lifetime. | |||
|
60 | /// We need to be protected by the GIL for thread-safety. | |||
|
61 | pub fn leak_immutable<T>( | |||
|
62 | &self, | |||
|
63 | py: Python, | |||
|
64 | data: &RefCell<T>, | |||
|
65 | ) -> PyResult<&'static T> { | |||
|
66 | if self.mutably_borrowed.get() { | |||
|
67 | return Err(AlreadyBorrowed::new( | |||
|
68 | py, | |||
|
69 | "Cannot borrow immutably while there is a \ | |||
|
70 | mutable reference in Python objects", | |||
|
71 | )); | |||
|
72 | } | |||
|
73 | let ptr = data.as_ptr(); | |||
|
74 | self.leak_count.replace(self.leak_count.get() + 1); | |||
|
75 | unsafe { Ok(&*ptr) } | |||
|
76 | } | |||
|
77 | ||||
|
78 | pub fn decrease_leak_count(&self, _py: Python, mutable: bool) { | |||
|
79 | self.leak_count | |||
|
80 | .replace(self.leak_count.get().saturating_sub(1)); | |||
|
81 | if mutable { | |||
|
82 | self.mutably_borrowed.replace(false); | |||
|
83 | } | |||
|
84 | } | |||
|
85 | } | |||
|
86 | ||||
|
87 | /// Holds a mutable reference to data shared between Python and Rust. | |||
|
88 | pub struct PyRefMut<'a, T> { | |||
|
89 | inner: RefMut<'a, T>, | |||
|
90 | py_shared_state: &'a PySharedState, | |||
|
91 | } | |||
|
92 | ||||
|
93 | impl<'a, T> PyRefMut<'a, T> { | |||
|
94 | fn new( | |||
|
95 | _py: Python<'a>, | |||
|
96 | inner: RefMut<'a, T>, | |||
|
97 | py_shared_state: &'a PySharedState, | |||
|
98 | ) -> Self { | |||
|
99 | Self { | |||
|
100 | inner, | |||
|
101 | py_shared_state, | |||
|
102 | } | |||
|
103 | } | |||
|
104 | } | |||
|
105 | ||||
|
106 | impl<'a, T> std::ops::Deref for PyRefMut<'a, T> { | |||
|
107 | type Target = RefMut<'a, T>; | |||
|
108 | ||||
|
109 | fn deref(&self) -> &Self::Target { | |||
|
110 | &self.inner | |||
|
111 | } | |||
|
112 | } | |||
|
113 | impl<'a, T> std::ops::DerefMut for PyRefMut<'a, T> { | |||
|
114 | fn deref_mut(&mut self) -> &mut Self::Target { | |||
|
115 | &mut self.inner | |||
|
116 | } | |||
|
117 | } | |||
|
118 | ||||
|
119 | impl<'a, T> Drop for PyRefMut<'a, T> { | |||
|
120 | fn drop(&mut self) { | |||
|
121 | let gil = Python::acquire_gil(); | |||
|
122 | let py = gil.python(); | |||
|
123 | self.py_shared_state.decrease_leak_count(py, true); | |||
|
124 | } | |||
|
125 | } | |||
|
126 | ||||
|
127 | /// Allows a `py_class!` generated struct to share references to one of its | |||
|
128 | /// data members with Python. | |||
|
129 | /// | |||
|
130 | /// # Warning | |||
|
131 | /// | |||
|
132 | /// The targeted `py_class!` needs to have the | |||
|
133 | /// `data py_shared_state: PySharedState;` data attribute to compile. | |||
|
134 | /// A better, more complicated macro is needed to automatically insert it, | |||
|
135 | /// but this one is not yet really battle tested (what happens when | |||
|
136 | /// multiple references are needed?). See the example below. | |||
|
137 | /// | |||
|
138 | /// TODO allow Python container types: for now, integration with the garbage | |||
|
139 | /// collector does not extend to Rust structs holding references to Python | |||
|
140 | /// objects. Should the need surface, `__traverse__` and `__clear__` will | |||
|
141 | /// need to be written as per the `rust-cpython` docs on GC integration. | |||
|
142 | /// | |||
|
143 | /// # Parameters | |||
|
144 | /// | |||
|
145 | /// * `$name` is the same identifier used in for `py_class!` macro call. | |||
|
146 | /// * `$inner_struct` is the identifier of the underlying Rust struct | |||
|
147 | /// * `$data_member` is the identifier of the data member of `$inner_struct` | |||
|
148 | /// that will be shared. | |||
|
149 | /// * `$leaked` is the identifier to give to the struct that will manage | |||
|
150 | /// references to `$name`, to be used for example in other macros like | |||
|
151 | /// `py_shared_mapping_iterator`. | |||
|
152 | /// | |||
|
153 | /// # Example | |||
|
154 | /// | |||
|
155 | /// ``` | |||
|
156 | /// struct MyStruct { | |||
|
157 | /// inner: Vec<u32>; | |||
|
158 | /// } | |||
|
159 | /// | |||
|
160 | /// py_class!(pub class MyType |py| { | |||
|
161 | /// data inner: RefCell<MyStruct>; | |||
|
162 | /// data py_shared_state: PySharedState; | |||
|
163 | /// }); | |||
|
164 | /// | |||
|
165 | /// py_shared_ref!(MyType, MyStruct, inner, MyTypeLeakedRef); | |||
|
166 | /// ``` | |||
|
167 | macro_rules! py_shared_ref { | |||
|
168 | ( | |||
|
169 | $name: ident, | |||
|
170 | $inner_struct: ident, | |||
|
171 | $data_member: ident, | |||
|
172 | $leaked: ident, | |||
|
173 | ) => { | |||
|
174 | impl $name { | |||
|
175 | fn borrow_mut<'a>( | |||
|
176 | &'a self, | |||
|
177 | py: Python<'a>, | |||
|
178 | ) -> PyResult<crate::ref_sharing::PyRefMut<'a, $inner_struct>> | |||
|
179 | { | |||
|
180 | self.py_shared_state(py) | |||
|
181 | .borrow_mut(py, self.$data_member(py).borrow_mut()) | |||
|
182 | } | |||
|
183 | ||||
|
184 | fn leak_immutable<'a>( | |||
|
185 | &'a self, | |||
|
186 | py: Python<'a>, | |||
|
187 | ) -> PyResult<&'static $inner_struct> { | |||
|
188 | self.py_shared_state(py) | |||
|
189 | .leak_immutable(py, self.$data_member(py)) | |||
|
190 | } | |||
|
191 | } | |||
|
192 | ||||
|
193 | /// Manage immutable references to `$name` leaked into Python | |||
|
194 | /// iterators. | |||
|
195 | /// | |||
|
196 | /// In truth, this does not represent leaked references themselves; | |||
|
197 | /// it is instead useful alongside them to manage them. | |||
|
198 | pub struct $leaked { | |||
|
199 | inner: $name, | |||
|
200 | } | |||
|
201 | ||||
|
202 | impl $leaked { | |||
|
203 | fn new(py: Python, inner: &$name) -> Self { | |||
|
204 | Self { | |||
|
205 | inner: inner.clone_ref(py), | |||
|
206 | } | |||
|
207 | } | |||
|
208 | } | |||
|
209 | ||||
|
210 | impl Drop for $leaked { | |||
|
211 | fn drop(&mut self) { | |||
|
212 | let gil = Python::acquire_gil(); | |||
|
213 | let py = gil.python(); | |||
|
214 | self.inner | |||
|
215 | .py_shared_state(py) | |||
|
216 | .decrease_leak_count(py, false); | |||
|
217 | } | |||
|
218 | } | |||
|
219 | }; | |||
|
220 | } | |||
|
221 | ||||
|
222 | /// Defines a `py_class!` that acts as a Python iterator over a Rust iterator. | |||
|
223 | macro_rules! py_shared_iterator_impl { | |||
|
224 | ( | |||
|
225 | $name: ident, | |||
|
226 | $leaked: ident, | |||
|
227 | $iterator_type: ty, | |||
|
228 | $success_func: expr, | |||
|
229 | $success_type: ty | |||
|
230 | ) => { | |||
|
231 | py_class!(pub class $name |py| { | |||
|
232 | data inner: RefCell<Option<$leaked>>; | |||
|
233 | data it: RefCell<$iterator_type>; | |||
|
234 | ||||
|
235 | def __next__(&self) -> PyResult<$success_type> { | |||
|
236 | let mut inner_opt = self.inner(py).borrow_mut(); | |||
|
237 | if inner_opt.is_some() { | |||
|
238 | match self.it(py).borrow_mut().next() { | |||
|
239 | None => { | |||
|
240 | // replace Some(inner) by None, drop $leaked | |||
|
241 | inner_opt.take(); | |||
|
242 | Ok(None) | |||
|
243 | } | |||
|
244 | Some(res) => { | |||
|
245 | $success_func(py, res) | |||
|
246 | } | |||
|
247 | } | |||
|
248 | } else { | |||
|
249 | Ok(None) | |||
|
250 | } | |||
|
251 | } | |||
|
252 | ||||
|
253 | def __iter__(&self) -> PyResult<Self> { | |||
|
254 | Ok(self.clone_ref(py)) | |||
|
255 | } | |||
|
256 | }); | |||
|
257 | ||||
|
258 | impl $name { | |||
|
259 | pub fn from_inner( | |||
|
260 | py: Python, | |||
|
261 | leaked: Option<$leaked>, | |||
|
262 | it: $iterator_type | |||
|
263 | ) -> PyResult<Self> { | |||
|
264 | Self::create_instance( | |||
|
265 | py, | |||
|
266 | RefCell::new(leaked), | |||
|
267 | RefCell::new(it) | |||
|
268 | ) | |||
|
269 | } | |||
|
270 | } | |||
|
271 | }; | |||
|
272 | } | |||
|
273 | ||||
|
274 | /// Defines a `py_class!` that acts as a Python mapping iterator over a Rust | |||
|
275 | /// iterator. | |||
|
276 | /// | |||
|
277 | /// TODO: this is a bit awkward to use, and a better (more complicated) | |||
|
278 | /// procedural macro would simplify the interface a lot. | |||
|
279 | /// | |||
|
280 | /// # Parameters | |||
|
281 | /// | |||
|
282 | /// * `$name` is the identifier to give to the resulting Rust struct. | |||
|
283 | /// * `$leaked` corresponds to `$leaked` in the matching `py_shared_ref!` call. | |||
|
284 | /// * `$key_type` is the type of the key in the mapping | |||
|
285 | /// * `$value_type` is the type of the value in the mapping | |||
|
286 | /// * `$success_func` is a function for processing the Rust `(key, value)` | |||
|
287 | /// tuple on iteration success, turning it into something Python understands. | |||
|
288 | /// * `$success_func` is the return type of `$success_func` | |||
|
289 | /// | |||
|
290 | /// # Example | |||
|
291 | /// | |||
|
292 | /// ``` | |||
|
293 | /// struct MyStruct { | |||
|
294 | /// inner: HashMap<Vec<u8>, Vec<u8>>; | |||
|
295 | /// } | |||
|
296 | /// | |||
|
297 | /// py_class!(pub class MyType |py| { | |||
|
298 | /// data inner: RefCell<MyStruct>; | |||
|
299 | /// data py_shared_state: PySharedState; | |||
|
300 | /// | |||
|
301 | /// def __iter__(&self) -> PyResult<MyTypeItemsIterator> { | |||
|
302 | /// MyTypeItemsIterator::create_instance( | |||
|
303 | /// py, | |||
|
304 | /// RefCell::new(Some(MyTypeLeakedRef::new(py, &self))), | |||
|
305 | /// RefCell::new(self.leak_immutable(py).iter()), | |||
|
306 | /// ) | |||
|
307 | /// } | |||
|
308 | /// }); | |||
|
309 | /// | |||
|
310 | /// impl MyType { | |||
|
311 | /// fn translate_key_value( | |||
|
312 | /// py: Python, | |||
|
313 | /// res: (&Vec<u8>, &Vec<u8>), | |||
|
314 | /// ) -> PyResult<Option<(PyBytes, PyBytes)>> { | |||
|
315 | /// let (f, entry) = res; | |||
|
316 | /// Ok(Some(( | |||
|
317 | /// PyBytes::new(py, f), | |||
|
318 | /// PyBytes::new(py, entry), | |||
|
319 | /// ))) | |||
|
320 | /// } | |||
|
321 | /// } | |||
|
322 | /// | |||
|
323 | /// py_shared_ref!(MyType, MyStruct, inner, MyTypeLeakedRef); | |||
|
324 | /// | |||
|
325 | /// py_shared_mapping_iterator!( | |||
|
326 | /// MyTypeItemsIterator, | |||
|
327 | /// MyTypeLeakedRef, | |||
|
328 | /// Vec<u8>, | |||
|
329 | /// Vec<u8>, | |||
|
330 | /// MyType::translate_key_value, | |||
|
331 | /// Option<(PyBytes, PyBytes)> | |||
|
332 | /// ); | |||
|
333 | /// ``` | |||
|
334 | #[allow(unused)] // Removed in a future patch | |||
|
335 | macro_rules! py_shared_mapping_iterator { | |||
|
336 | ( | |||
|
337 | $name:ident, | |||
|
338 | $leaked:ident, | |||
|
339 | $key_type: ty, | |||
|
340 | $value_type: ty, | |||
|
341 | $success_func: path, | |||
|
342 | $success_type: ty | |||
|
343 | ) => { | |||
|
344 | py_shared_iterator_impl!( | |||
|
345 | $name, | |||
|
346 | $leaked, | |||
|
347 | Box< | |||
|
348 | Iterator<Item = (&'static $key_type, &'static $value_type)> | |||
|
349 | + Send, | |||
|
350 | >, | |||
|
351 | $success_func, | |||
|
352 | $success_type | |||
|
353 | ); | |||
|
354 | }; | |||
|
355 | } | |||
|
356 | ||||
|
357 | /// Works basically the same as `py_shared_mapping_iterator`, but with only a | |||
|
358 | /// key. | |||
|
359 | macro_rules! py_shared_sequence_iterator { | |||
|
360 | ( | |||
|
361 | $name:ident, | |||
|
362 | $leaked:ident, | |||
|
363 | $key_type: ty, | |||
|
364 | $success_func: path, | |||
|
365 | $success_type: ty | |||
|
366 | ) => { | |||
|
367 | py_shared_iterator_impl!( | |||
|
368 | $name, | |||
|
369 | $leaked, | |||
|
370 | Box<Iterator<Item = &'static $key_type> + Send>, | |||
|
371 | $success_func, | |||
|
372 | $success_type | |||
|
373 | ); | |||
|
374 | }; | |||
|
375 | } |
@@ -9,21 +9,22 b'' | |||||
9 | //! `hg-core` package. |
|
9 | //! `hg-core` package. | |
10 |
|
10 | |||
11 | use std::cell::RefCell; |
|
11 | use std::cell::RefCell; | |
|
12 | use std::convert::TryInto; | |||
12 |
|
13 | |||
13 | use cpython::{ |
|
14 | use cpython::{ | |
14 | exc, ObjectProtocol, PyBytes, PyDict, PyErr, PyObject, PyResult, |
|
15 | exc, ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult, | |
15 | PythonObject, ToPyObject, |
|
16 | Python, | |
16 | }; |
|
17 | }; | |
17 |
|
18 | |||
18 | use crate::dirstate::extract_dirstate; |
|
19 | use crate::{dirstate::extract_dirstate, ref_sharing::PySharedState}; | |
19 | use hg::{ |
|
20 | use hg::{ | |
20 | DirsIterable, DirsMultiset, DirstateMapError, DirstateParseError, |
|
21 | DirsIterable, DirsMultiset, DirstateMapError, DirstateParseError, | |
21 | EntryState, |
|
22 | EntryState, | |
22 | }; |
|
23 | }; | |
23 | use std::convert::TryInto; |
|
|||
24 |
|
24 | |||
25 | py_class!(pub class Dirs |py| { |
|
25 | py_class!(pub class Dirs |py| { | |
26 |
data |
|
26 | data inner: RefCell<DirsMultiset>; | |
|
27 | data py_shared_state: PySharedState; | |||
27 |
|
28 | |||
28 | // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes |
|
29 | // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes | |
29 | // a `list`) |
|
30 | // a `list`) | |
@@ -59,18 +60,22 b' py_class!(pub class Dirs |py| {' | |||||
59 | ) |
|
60 | ) | |
60 | }; |
|
61 | }; | |
61 |
|
62 | |||
62 |
Self::create_instance( |
|
63 | Self::create_instance( | |
|
64 | py, | |||
|
65 | RefCell::new(inner), | |||
|
66 | PySharedState::default() | |||
|
67 | ) | |||
63 | } |
|
68 | } | |
64 |
|
69 | |||
65 | def addpath(&self, path: PyObject) -> PyResult<PyObject> { |
|
70 | def addpath(&self, path: PyObject) -> PyResult<PyObject> { | |
66 |
self. |
|
71 | self.borrow_mut(py)?.add_path( | |
67 | path.extract::<PyBytes>(py)?.data(py), |
|
72 | path.extract::<PyBytes>(py)?.data(py), | |
68 | ); |
|
73 | ); | |
69 | Ok(py.None()) |
|
74 | Ok(py.None()) | |
70 | } |
|
75 | } | |
71 |
|
76 | |||
72 | def delpath(&self, path: PyObject) -> PyResult<PyObject> { |
|
77 | def delpath(&self, path: PyObject) -> PyResult<PyObject> { | |
73 |
self. |
|
78 | self.borrow_mut(py)?.delete_path( | |
74 | path.extract::<PyBytes>(py)?.data(py), |
|
79 | path.extract::<PyBytes>(py)?.data(py), | |
75 | ) |
|
80 | ) | |
76 | .and(Ok(py.None())) |
|
81 | .and(Ok(py.None())) | |
@@ -88,26 +93,38 b' py_class!(pub class Dirs |py| {' | |||||
88 | } |
|
93 | } | |
89 | }) |
|
94 | }) | |
90 | } |
|
95 | } | |
91 |
|
96 | def __iter__(&self) -> PyResult<DirsMultisetKeysIterator> { | ||
92 | // This is really inefficient on top of being ugly, but it's an easy way |
|
97 | DirsMultisetKeysIterator::create_instance( | |
93 | // of having it work to continue working on the rest of the module |
|
98 | py, | |
94 | // hopefully bypassing Python entirely pretty soon. |
|
99 | RefCell::new(Some(DirsMultisetLeakedRef::new(py, &self))), | |
95 | def __iter__(&self) -> PyResult<PyObject> { |
|
100 | RefCell::new(Box::new(self.leak_immutable(py)?.iter())), | |
96 | let dirs = self.dirs_map(py).borrow(); |
|
101 | ) | |
97 | let dirs: Vec<_> = dirs |
|
|||
98 | .iter() |
|
|||
99 | .map(|d| PyBytes::new(py, d)) |
|
|||
100 | .collect(); |
|
|||
101 | dirs.to_py_object(py) |
|
|||
102 | .into_object() |
|
|||
103 | .iter(py) |
|
|||
104 | .map(|o| o.into_object()) |
|
|||
105 | } |
|
102 | } | |
106 |
|
103 | |||
107 | def __contains__(&self, item: PyObject) -> PyResult<bool> { |
|
104 | def __contains__(&self, item: PyObject) -> PyResult<bool> { | |
108 | Ok(self |
|
105 | Ok(self | |
109 |
. |
|
106 | .inner(py) | |
110 | .borrow() |
|
107 | .borrow() | |
111 | .contains(item.extract::<PyBytes>(py)?.data(py).as_ref())) |
|
108 | .contains(item.extract::<PyBytes>(py)?.data(py).as_ref())) | |
112 | } |
|
109 | } | |
113 | }); |
|
110 | }); | |
|
111 | ||||
|
112 | py_shared_ref!(Dirs, DirsMultiset, inner, DirsMultisetLeakedRef,); | |||
|
113 | ||||
|
114 | impl Dirs { | |||
|
115 | pub fn from_inner(py: Python, d: DirsMultiset) -> PyResult<Self> { | |||
|
116 | Self::create_instance(py, RefCell::new(d), PySharedState::default()) | |||
|
117 | } | |||
|
118 | ||||
|
119 | fn translate_key(py: Python, res: &Vec<u8>) -> PyResult<Option<PyBytes>> { | |||
|
120 | Ok(Some(PyBytes::new(py, res))) | |||
|
121 | } | |||
|
122 | } | |||
|
123 | ||||
|
124 | py_shared_sequence_iterator!( | |||
|
125 | DirsMultisetKeysIterator, | |||
|
126 | DirsMultisetLeakedRef, | |||
|
127 | Vec<u8>, | |||
|
128 | Dirs::translate_key, | |||
|
129 | Option<PyBytes> | |||
|
130 | ); |
@@ -67,3 +67,5 b' impl PatternFileError {' | |||||
67 | } |
|
67 | } | |
68 | } |
|
68 | } | |
69 | } |
|
69 | } | |
|
70 | ||||
|
71 | py_exception!(shared_ref, AlreadyBorrowed, RuntimeError); |
@@ -27,6 +27,8 b' extern crate cpython;' | |||||
27 | pub mod ancestors; |
|
27 | pub mod ancestors; | |
28 | mod cindex; |
|
28 | mod cindex; | |
29 | mod conversion; |
|
29 | mod conversion; | |
|
30 | #[macro_use] | |||
|
31 | pub mod ref_sharing; | |||
30 | pub mod dagops; |
|
32 | pub mod dagops; | |
31 | pub mod dirstate; |
|
33 | pub mod dirstate; | |
32 | pub mod parsers; |
|
34 | pub mod parsers; |
General Comments 0
You need to be logged in to leave comments.
Login now