Mercurial > hg
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 ); |