comparison rust/hg-cpython/src/dirstate/dirs_multiset.rs @ 42752:30320c7bf79f

rust-cpython: add macro for sharing references Following an experiment done by Georges Racinet, we now have a working way of sharing references between Python and Rust. This is needed in many points of the codebase, for example every time we need to expose an iterator to a Rust-backed Python class. In a few words, references are (unsafely) marked as `'static` and coupled with manual reference counting; we are doing manual borrow-checking. This changes introduces two declarative macro to help reduce boilerplate. While it is better than not using macros, they are not perfect. They need to: - Integrate with the garbage collector for container types (not needed as of yet), as stated in the docstring - Allow for leaking multiple attributes at the same time - Inject the `py_shared_state` data attribute in `py_class`-generated structs - Automatically namespace the functions and attributes they generate For at least the last two points, we will need to write a procedural macro instead of a declarative one. While this reference-sharing mechanism is being ironed out I thought it best not to implement it yet. Lastly, and implementation detail renders our Rust-backed Python iterators too strict to be proper drop-in replacements, as will be illustrated in a future patch: if the data structure referenced by a non-depleted iterator is mutated, an `AlreadyBorrowed` exception is raised, whereas Python would allow it, only to raise a `RuntimeError` if `next` is called on said iterator. This will have to be addressed at some point. Differential Revision: https://phab.mercurial-scm.org/D6631
author Raphaël Gomès <rgomes@octobus.net>
date Tue, 09 Jul 2019 15:15:54 +0200
parents 849e744b925d
children 2e1f74cc3350
comparison
equal deleted inserted replaced
42751:4b3b27d567d5 42752:30320c7bf79f
7 7
8 //! Bindings for the `hg::dirstate::dirs_multiset` file provided by the 8 //! Bindings for the `hg::dirstate::dirs_multiset` file provided by the
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 dirs_map: RefCell<DirsMultiset>; 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`)
30 def __new__( 31 def __new__(
31 _cls, 32 _cls,
57 DirsIterable::Manifest(&map?), 58 DirsIterable::Manifest(&map?),
58 skip_state, 59 skip_state,
59 ) 60 )
60 }; 61 };
61 62
62 Self::create_instance(py, RefCell::new(inner)) 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.dirs_map(py).borrow_mut().add_path( 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.dirs_map(py).borrow_mut().delete_path( 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()))
77 .or_else(|e| { 82 .or_else(|e| {
78 match e { 83 match e {
86 Ok(py.None()) 91 Ok(py.None())
87 } 92 }
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 .dirs_map(py) 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 );