Mercurial > hg
comparison rust/hg-cpython/src/ancestors.rs @ 41188:006c9ce486fa
rust-cpython: bindings for MissingAncestors
The exposition is rather straightforward, except for the
remove_ancestors_from() method, which forces us to an inefficient
conversion between Python sets and Rust HashSets.
Two alternatives are proposed in comments:
- changing the inner API to "emit" the revision numbers to discard
this would be a substantial change, and it would be better only in the
cases where there are more to retain than to discard
- mutating the Python set directly: this would force us to define an abstract
`RevisionSet` trait, and implement it both for plain `HashSet` and for
a struct enclosing a Python set with the GIL marker `Python<'p>`, also a non
trivial effort.
The main (and seemingly only) caller of this method being
`mercurial.setdiscovery`, which is currently undergoing serious refactoring,
it's not clear whether these improvements would be worth the effort right now,
so we're leaving it as-is.
Also, in `get_bases()` (will also be used by `setdiscovery`), we'd prefer
to build a Python set directly, but we resort to returning a tuple, waiting
to hear back from our PR onto rust-cpython about that
Differential Revision: https://phab.mercurial-scm.org/D5550
author | Georges Racinet <georges.racinet@octobus.net> |
---|---|
date | Fri, 30 Nov 2018 20:05:34 +0100 |
parents | 3bf6979a1785 |
children | ff333620a4cc |
comparison
equal
deleted
inserted
replaced
41187:3bf6979a1785 | 41188:006c9ce486fa |
---|---|
13 //! - [`LazyAncestors`] is the Rust implementation of | 13 //! - [`LazyAncestors`] is the Rust implementation of |
14 //! `mercurial.ancestor.lazyancestors`. | 14 //! `mercurial.ancestor.lazyancestors`. |
15 //! The only difference is that it is instantiated with a C `parsers.index` | 15 //! The only difference is that it is instantiated with a C `parsers.index` |
16 //! instance instead of a parents function. | 16 //! instance instead of a parents function. |
17 //! | 17 //! |
18 //! - [`MissingAncestors`] is the Rust implementation of | |
19 //! `mercurial.ancestor.incrementalmissingancestors`. | |
20 //! | |
21 //! API differences: | |
22 //! + it is instantiated with a C `parsers.index` | |
23 //! instance instead of a parents function. | |
24 //! + `MissingAncestors.bases` is a method returning a tuple instead of | |
25 //! a set-valued attribute. We could return a Python set easily if our | |
26 //! [PySet PR](https://github.com/dgrunwald/rust-cpython/pull/165) | |
27 //! is accepted. | |
28 //! | |
18 //! - [`AncestorsIterator`] is the Rust counterpart of the | 29 //! - [`AncestorsIterator`] is the Rust counterpart of the |
19 //! `ancestor._lazyancestorsiter` Python generator. | 30 //! `ancestor._lazyancestorsiter` Python generator. |
20 //! From Python, instances of this should be mainly obtained by calling | 31 //! From Python, instances of this should be mainly obtained by calling |
21 //! `iter()` on a [`LazyAncestors`] instance. | 32 //! `iter()` on a [`LazyAncestors`] instance. |
22 //! | 33 //! |
23 //! [`LazyAncestors`]: struct.LazyAncestors.html | 34 //! [`LazyAncestors`]: struct.LazyAncestors.html |
35 //! [`MissingAncestors`]: struct.MissingAncestors.html | |
24 //! [`AncestorsIterator`]: struct.AncestorsIterator.html | 36 //! [`AncestorsIterator`]: struct.AncestorsIterator.html |
25 use cindex::Index; | 37 use cindex::Index; |
26 use cpython::{ | 38 use cpython::{ |
27 ObjectProtocol, PyClone, PyDict, PyModule, PyObject, PyResult, Python, | 39 ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, |
40 PyResult, PyTuple, Python, PythonObject, ToPyObject, | |
28 }; | 41 }; |
29 use exceptions::GraphError; | 42 use exceptions::GraphError; |
30 use hg::Revision; | 43 use hg::Revision; |
31 use hg::{AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy}; | 44 use hg::{ |
45 AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy, | |
46 MissingAncestors as CoreMissing, | |
47 }; | |
32 use std::cell::RefCell; | 48 use std::cell::RefCell; |
33 use std::iter::FromIterator; | 49 use std::iter::FromIterator; |
50 use std::collections::HashSet; | |
34 | 51 |
35 /// Utility function to convert a Python iterable into various collections | 52 /// Utility function to convert a Python iterable into various collections |
36 /// | 53 /// |
37 /// We need this in particular to feed to various methods of inner objects | 54 /// We need this in particular to feed to various methods of inner objects |
38 /// with `impl IntoIterator<Item=Revision>` arguments, because | 55 /// with `impl IntoIterator<Item=Revision>` arguments, because |
117 Self::create_instance(py, RefCell::new(Box::new(lazy))) | 134 Self::create_instance(py, RefCell::new(Box::new(lazy))) |
118 } | 135 } |
119 | 136 |
120 }); | 137 }); |
121 | 138 |
122 /// Create the module, with `__package__` given from parent | 139 py_class!(pub class MissingAncestors |py| { |
140 data inner: RefCell<Box<CoreMissing<Index>>>; | |
141 | |
142 def __new__(_cls, index: PyObject, bases: PyObject) -> PyResult<MissingAncestors> { | |
143 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?; | |
144 let inner = CoreMissing::new(Index::new(py, index)?, bases_vec); | |
145 MissingAncestors::create_instance(py, RefCell::new(Box::new(inner))) | |
146 } | |
147 | |
148 def hasbases(&self) -> PyResult<bool> { | |
149 Ok(self.inner(py).borrow().has_bases()) | |
150 } | |
151 | |
152 def addbases(&self, bases: PyObject) -> PyResult<PyObject> { | |
153 let mut inner = self.inner(py).borrow_mut(); | |
154 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?; | |
155 inner.add_bases(bases_vec); | |
156 // cpython doc has examples with PyResult<()> but this gives me | |
157 // the trait `cpython::ToPyObject` is not implemented for `()` | |
158 // so let's return an explicit None | |
159 Ok(py.None()) | |
160 } | |
161 | |
162 def bases(&self) -> PyResult<PyTuple> { | |
163 let inner = self.inner(py).borrow(); | |
164 let bases_set = inner.get_bases(); | |
165 // convert as Python tuple TODO how to return a proper Python set? | |
166 let mut bases_vec: Vec<PyObject> = Vec::with_capacity( | |
167 bases_set.len()); | |
168 for rev in bases_set { | |
169 bases_vec.push(rev.to_py_object(py).into_object()); | |
170 } | |
171 Ok(PyTuple::new(py, bases_vec.as_slice())) | |
172 } | |
173 | |
174 def removeancestorsfrom(&self, revs: PyObject) -> PyResult<PyObject> { | |
175 let mut inner = self.inner(py).borrow_mut(); | |
176 // this is very lame: we convert to a Rust set, update it in place | |
177 // and then convert back to Python, only to have Python remove the | |
178 // excess (thankfully, Python is happy with a list or even an iterator) | |
179 // Leads to improve this: | |
180 // - have the CoreMissing instead do something emit revisions to | |
181 // discard | |
182 // - define a trait for sets of revisions in the core and implement | |
183 // it for a Python set rewrapped with the GIL marker | |
184 let mut revs_pyset: HashSet<Revision> = rev_pyiter_collect(py, &revs)?; | |
185 inner.remove_ancestors_from(&mut revs_pyset) | |
186 .map_err(|e| GraphError::pynew(py, e))?; | |
187 | |
188 // convert as Python list | |
189 let mut remaining_pyint_vec: Vec<PyObject> = Vec::with_capacity( | |
190 revs_pyset.len()); | |
191 for rev in revs_pyset { | |
192 remaining_pyint_vec.push(rev.to_py_object(py).into_object()); | |
193 } | |
194 let remaining_pylist = PyList::new(py, remaining_pyint_vec.as_slice()); | |
195 revs.call_method(py, "intersection_update", (remaining_pylist, ), None) | |
196 } | |
197 | |
198 def missingancestors(&self, revs: PyObject) -> PyResult<PyList> { | |
199 let mut inner = self.inner(py).borrow_mut(); | |
200 let revs_vec: Vec<Revision> = rev_pyiter_collect(py, &revs)?; | |
201 let missing_vec = match inner.missing_ancestors(revs_vec) { | |
202 Ok(missing) => missing, | |
203 Err(e) => { | |
204 return Err(GraphError::pynew(py, e)); | |
205 } | |
206 }; | |
207 // convert as Python list | |
208 let mut missing_pyint_vec: Vec<PyObject> = Vec::with_capacity( | |
209 missing_vec.len()); | |
210 for rev in missing_vec { | |
211 missing_pyint_vec.push(rev.to_py_object(py).into_object()); | |
212 } | |
213 Ok(PyList::new(py, missing_pyint_vec.as_slice())) | |
214 } | |
215 }); | |
216 | |
217 /// Create the module, with __package__ given from parent | |
123 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> { | 218 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> { |
124 let dotted_name = &format!("{}.ancestor", package); | 219 let dotted_name = &format!("{}.ancestor", package); |
125 let m = PyModule::new(py, dotted_name)?; | 220 let m = PyModule::new(py, dotted_name)?; |
126 m.add(py, "__package__", package)?; | 221 m.add(py, "__package__", package)?; |
127 m.add( | 222 m.add( |
129 "__doc__", | 224 "__doc__", |
130 "Generic DAG ancestor algorithms - Rust implementation", | 225 "Generic DAG ancestor algorithms - Rust implementation", |
131 )?; | 226 )?; |
132 m.add_class::<AncestorsIterator>(py)?; | 227 m.add_class::<AncestorsIterator>(py)?; |
133 m.add_class::<LazyAncestors>(py)?; | 228 m.add_class::<LazyAncestors>(py)?; |
229 m.add_class::<MissingAncestors>(py)?; | |
134 | 230 |
135 let sys = PyModule::import(py, "sys")?; | 231 let sys = PyModule::import(py, "sys")?; |
136 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; | 232 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; |
137 sys_modules.set_item(py, dotted_name, &m)?; | 233 sys_modules.set_item(py, dotted_name, &m)?; |
138 // Example C code (see pyexpat.c and import.c) will "give away the | 234 // Example C code (see pyexpat.c and import.c) will "give away the |