|
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 } |