We are not doing any is_ancestor call anymore. So we can drop that logic and
associated arguments.
Details
Details
Diff Detail
Diff Detail
- Repository
- rHG Mercurial
- Branch
- default
- Lint
No Linters Available - Unit
No Unit Test Coverage
( )
We are not doing any is_ancestor call anymore. So we can drop that logic and
associated arguments.
No Linters Available |
No Unit Test Coverage |
Path | Packages | |||
---|---|---|---|---|
M | mercurial/copies.py (2 lines) | |||
M | rust/hg-core/src/copy_tracing.rs (95 lines) | |||
M | rust/hg-cpython/src/copy_tracing.rs (27 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
ab140755dea9 | cb8e24c590ef | Pierre-Yves David | Dec 15 2020, 12:22 PM |
It returns the aggregated copies information for `targetrev`. | It returns the aggregated copies information for `targetrev`. | ||||
""" | """ | ||||
alwaysmatch = match.always() | alwaysmatch = match.always() | ||||
if rustmod is not None: | if rustmod is not None: | ||||
final_copies = rustmod.combine_changeset_copies( | final_copies = rustmod.combine_changeset_copies( | ||||
list(revs), children_count, targetrev, revinfo, isancestor | list(revs), children_count, targetrev, revinfo | ||||
) | ) | ||||
else: | else: | ||||
isancestor = cached_is_ancestor(isancestor) | isancestor = cached_is_ancestor(isancestor) | ||||
all_copies = {} | all_copies = {} | ||||
# iterate over all the "children" side of copy tracing "edge" | # iterate over all the "children" side of copy tracing "edge" | ||||
for current_rev in revs: | for current_rev in revs: | ||||
p1, p2, changes = revinfo(current_rev) | p1, p2, changes = revinfo(current_rev) |
}; | }; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
MergeCase::Normal | MergeCase::Normal | ||||
} | } | ||||
} | } | ||||
/// A struct responsible for answering "is X ancestors of Y" quickly | |||||
/// | |||||
/// The structure will delegate ancestors call to a callback, and cache the | |||||
/// result. | |||||
#[derive(Debug)] | |||||
struct AncestorOracle<'a, A: Fn(Revision, Revision) -> bool> { | |||||
inner: &'a A, | |||||
pairs: HashMap<(Revision, Revision), bool>, | |||||
} | |||||
impl<'a, A: Fn(Revision, Revision) -> bool> AncestorOracle<'a, A> { | |||||
fn new(func: &'a A) -> Self { | |||||
Self { | |||||
inner: func, | |||||
pairs: HashMap::default(), | |||||
} | |||||
} | |||||
fn record_overwrite(&mut self, anc: Revision, desc: Revision) { | |||||
self.pairs.insert((anc, desc), true); | |||||
} | |||||
/// returns `true` if `anc` is an ancestors of `desc`, `false` otherwise | |||||
fn is_overwrite(&mut self, anc: Revision, desc: Revision) -> bool { | |||||
if anc > desc { | |||||
false | |||||
} else if anc == desc { | |||||
true | |||||
} else { | |||||
if let Some(b) = self.pairs.get(&(anc, desc)) { | |||||
*b | |||||
} else { | |||||
let b = (self.inner)(anc, desc); | |||||
self.pairs.insert((anc, desc), b); | |||||
b | |||||
} | |||||
} | |||||
} | |||||
} | |||||
struct ActionsIterator<'a> { | struct ActionsIterator<'a> { | ||||
changes: &'a ChangedFiles<'a>, | changes: &'a ChangedFiles<'a>, | ||||
parent: Parent, | parent: Parent, | ||||
current: u32, | current: u32, | ||||
} | } | ||||
impl<'a> Iterator for ActionsIterator<'a> { | impl<'a> Iterator for ActionsIterator<'a> { | ||||
type Item = Action<'a>; | type Item = Action<'a>; | ||||
/// children: a {parent ? [childrens]} mapping | /// children: a {parent ? [childrens]} mapping | ||||
/// target_rev: the final revision we are combining copies to | /// target_rev: the final revision we are combining copies to | ||||
/// rev_info(rev): callback to get revision information: | /// rev_info(rev): callback to get revision information: | ||||
/// * first parent | /// * first parent | ||||
/// * second parent | /// * second parent | ||||
/// * ChangedFiles | /// * ChangedFiles | ||||
/// isancestors(low_rev, high_rev): callback to check if a revision is an | /// isancestors(low_rev, high_rev): callback to check if a revision is an | ||||
/// ancestor of another | /// ancestor of another | ||||
pub fn combine_changeset_copies<A: Fn(Revision, Revision) -> bool, D>( | pub fn combine_changeset_copies<D>( | ||||
revs: Vec<Revision>, | revs: Vec<Revision>, | ||||
mut children_count: HashMap<Revision, usize>, | mut children_count: HashMap<Revision, usize>, | ||||
target_rev: Revision, | target_rev: Revision, | ||||
rev_info: RevInfoMaker<D>, | rev_info: RevInfoMaker<D>, | ||||
is_ancestor: &A, | |||||
) -> PathCopies { | ) -> PathCopies { | ||||
let mut all_copies = HashMap::new(); | let mut all_copies = HashMap::new(); | ||||
let mut oracle = AncestorOracle::new(is_ancestor); | |||||
let mut path_map = TwoWayPathMap::default(); | let mut path_map = TwoWayPathMap::default(); | ||||
for rev in revs { | for rev in revs { | ||||
let mut d: DataHolder<D> = DataHolder { data: None }; | let mut d: DataHolder<D> = DataHolder { data: None }; | ||||
let (p1, p2, changes) = rev_info(rev, &mut d); | let (p1, p2, changes) = rev_info(rev, &mut d); | ||||
// We will chain the copies information accumulated for the parent with | // We will chain the copies information accumulated for the parent with | ||||
// the individual copies information the curent revision. Creating a | // the individual copies information the curent revision. Creating a | ||||
// new TimeStampedPath for each `rev` โ `children` vertex. | // new TimeStampedPath for each `rev` โ `children` vertex. | ||||
let mut copies: Option<InternalPathCopies> = None; | let mut copies: Option<InternalPathCopies> = None; | ||||
if p1 != NULL_REVISION { | if p1 != NULL_REVISION { | ||||
// Retrieve data computed in a previous iteration | // Retrieve data computed in a previous iteration | ||||
let parent_copies = get_and_clean_parent_copies( | let parent_copies = get_and_clean_parent_copies( | ||||
&mut all_copies, | &mut all_copies, | ||||
&mut children_count, | &mut children_count, | ||||
p1, | p1, | ||||
); | ); | ||||
if let Some(parent_copies) = parent_copies { | if let Some(parent_copies) = parent_copies { | ||||
// combine it with data for that revision | // combine it with data for that revision | ||||
let vertex_copies = add_from_changes( | let vertex_copies = add_from_changes( | ||||
&mut path_map, | &mut path_map, | ||||
&mut oracle, | |||||
&parent_copies, | &parent_copies, | ||||
&changes, | &changes, | ||||
Parent::FirstParent, | Parent::FirstParent, | ||||
rev, | rev, | ||||
); | ); | ||||
// keep that data around for potential later combination | // keep that data around for potential later combination | ||||
copies = Some(vertex_copies); | copies = Some(vertex_copies); | ||||
} | } | ||||
} | } | ||||
if p2 != NULL_REVISION { | if p2 != NULL_REVISION { | ||||
// Retrieve data computed in a previous iteration | // Retrieve data computed in a previous iteration | ||||
let parent_copies = get_and_clean_parent_copies( | let parent_copies = get_and_clean_parent_copies( | ||||
&mut all_copies, | &mut all_copies, | ||||
&mut children_count, | &mut children_count, | ||||
p2, | p2, | ||||
); | ); | ||||
if let Some(parent_copies) = parent_copies { | if let Some(parent_copies) = parent_copies { | ||||
// combine it with data for that revision | // combine it with data for that revision | ||||
let vertex_copies = add_from_changes( | let vertex_copies = add_from_changes( | ||||
&mut path_map, | &mut path_map, | ||||
&mut oracle, | |||||
&parent_copies, | &parent_copies, | ||||
&changes, | &changes, | ||||
Parent::SecondParent, | Parent::SecondParent, | ||||
rev, | rev, | ||||
); | ); | ||||
copies = match copies { | copies = match copies { | ||||
None => Some(vertex_copies), | None => Some(vertex_copies), | ||||
// Merge has two parents needs to combines their copy | // Merge has two parents needs to combines their copy | ||||
// information. | // information. | ||||
// | // | ||||
// If we got data from both parents, We need to combine | // If we got data from both parents, We need to combine | ||||
// them. | // them. | ||||
Some(copies) => Some(merge_copies_dict( | Some(copies) => Some(merge_copies_dict( | ||||
&path_map, | &path_map, | ||||
rev, | rev, | ||||
vertex_copies, | vertex_copies, | ||||
copies, | copies, | ||||
&changes, | &changes, | ||||
&mut oracle, | |||||
)), | )), | ||||
}; | }; | ||||
} | } | ||||
} | } | ||||
match copies { | match copies { | ||||
Some(copies) => { | Some(copies) => { | ||||
all_copies.insert(rev, copies); | all_copies.insert(rev, copies); | ||||
} | } | ||||
Some(c) => Some(c.clone()), | Some(c) => Some(c.clone()), | ||||
None => Some(InternalPathCopies::default()), | None => Some(InternalPathCopies::default()), | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/// Combine ChangedFiles with some existing PathCopies information and return | /// Combine ChangedFiles with some existing PathCopies information and return | ||||
/// the result | /// the result | ||||
fn add_from_changes<A: Fn(Revision, Revision) -> bool>( | fn add_from_changes( | ||||
path_map: &mut TwoWayPathMap, | path_map: &mut TwoWayPathMap, | ||||
oracle: &mut AncestorOracle<A>, | |||||
base_copies: &InternalPathCopies, | base_copies: &InternalPathCopies, | ||||
changes: &ChangedFiles, | changes: &ChangedFiles, | ||||
parent: Parent, | parent: Parent, | ||||
current_rev: Revision, | current_rev: Revision, | ||||
) -> InternalPathCopies { | ) -> InternalPathCopies { | ||||
let mut copies = base_copies.clone(); | let mut copies = base_copies.clone(); | ||||
for action in changes.iter_actions(parent) { | for action in changes.iter_actions(parent) { | ||||
match action { | match action { | ||||
// information. See merge_copies_dict for details. | // information. See merge_copies_dict for details. | ||||
match copies.entry(dest) { | match copies.entry(dest) { | ||||
Entry::Vacant(slot) => { | Entry::Vacant(slot) => { | ||||
let ttpc = CopySource::new(current_rev, entry); | let ttpc = CopySource::new(current_rev, entry); | ||||
slot.insert(ttpc); | slot.insert(ttpc); | ||||
} | } | ||||
Entry::Occupied(mut slot) => { | Entry::Occupied(mut slot) => { | ||||
let ttpc = slot.get_mut(); | let ttpc = slot.get_mut(); | ||||
oracle.record_overwrite(ttpc.rev, current_rev); | |||||
ttpc.overwrite(current_rev, entry); | ttpc.overwrite(current_rev, entry); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
Action::Removed(deleted_path) => { | Action::Removed(deleted_path) => { | ||||
// We must drop copy information for removed file. | // We must drop copy information for removed file. | ||||
// | // | ||||
// We need to explicitly record them as dropped to | // We need to explicitly record them as dropped to | ||||
// propagate this information when merging two | // propagate this information when merging two | ||||
// InternalPathCopies object. | // InternalPathCopies object. | ||||
let deleted = path_map.tokenize(deleted_path); | let deleted = path_map.tokenize(deleted_path); | ||||
copies.entry(deleted).and_modify(|old| { | copies.entry(deleted).and_modify(|old| { | ||||
oracle.record_overwrite(old.rev, current_rev); | |||||
old.mark_delete(current_rev); | old.mark_delete(current_rev); | ||||
}); | }); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
copies | copies | ||||
} | } | ||||
/// merge two copies-mapping together, minor and major | /// merge two copies-mapping together, minor and major | ||||
/// | /// | ||||
/// In case of conflict, value from "major" will be picked, unless in some | /// In case of conflict, value from "major" will be picked, unless in some | ||||
/// cases. See inline documentation for details. | /// cases. See inline documentation for details. | ||||
fn merge_copies_dict<A: Fn(Revision, Revision) -> bool>( | fn merge_copies_dict( | ||||
path_map: &TwoWayPathMap, | path_map: &TwoWayPathMap, | ||||
current_merge: Revision, | current_merge: Revision, | ||||
mut minor: InternalPathCopies, | mut minor: InternalPathCopies, | ||||
mut major: InternalPathCopies, | mut major: InternalPathCopies, | ||||
changes: &ChangedFiles, | changes: &ChangedFiles, | ||||
oracle: &mut AncestorOracle<A>, | |||||
) -> InternalPathCopies { | ) -> InternalPathCopies { | ||||
// This closure exist as temporary help while multiple developper are | // This closure exist as temporary help while multiple developper are | ||||
// actively working on this code. Feel free to re-inline it once this | // actively working on this code. Feel free to re-inline it once this | ||||
// code is more settled. | // code is more settled. | ||||
let cmp_value = |oracle: &mut AncestorOracle<A>, | let cmp_value = | ||||
dest: &PathToken, | |dest: &PathToken, src_minor: &CopySource, src_major: &CopySource| { | ||||
src_minor: &CopySource, | |||||
src_major: &CopySource| { | |||||
compare_value( | compare_value( | ||||
path_map, | path_map, | ||||
current_merge, | current_merge, | ||||
changes, | changes, | ||||
oracle, | |||||
dest, | dest, | ||||
src_minor, | src_minor, | ||||
src_major, | src_major, | ||||
) | ) | ||||
}; | }; | ||||
if minor.is_empty() { | if minor.is_empty() { | ||||
major | major | ||||
} else if major.is_empty() { | } else if major.is_empty() { | ||||
minor | minor | ||||
} else if minor.len() * 2 < major.len() { | } else if minor.len() * 2 < major.len() { | ||||
// Lets says we are merging two InternalPathCopies instance A and B. | // Lets says we are merging two InternalPathCopies instance A and B. | ||||
// | // | ||||
// If A contains N items, the merge result will never contains more | // If A contains N items, the merge result will never contains more | ||||
for (dest, src_minor) in minor { | for (dest, src_minor) in minor { | ||||
let src_major = major.get(&dest); | let src_major = major.get(&dest); | ||||
match src_major { | match src_major { | ||||
None => { | None => { | ||||
major.insert(dest, src_minor); | major.insert(dest, src_minor); | ||||
} | } | ||||
Some(src_major) => { | Some(src_major) => { | ||||
let (pick, overwrite) = | let (pick, overwrite) = | ||||
cmp_value(oracle, &dest, &src_minor, src_major); | cmp_value(&dest, &src_minor, src_major); | ||||
if overwrite { | if overwrite { | ||||
oracle.record_overwrite(src_minor.rev, current_merge); | |||||
oracle.record_overwrite(src_major.rev, current_merge); | |||||
let src = match pick { | let src = match pick { | ||||
MergePick::Major => CopySource::new_from_merge( | MergePick::Major => CopySource::new_from_merge( | ||||
current_merge, | current_merge, | ||||
src_major, | src_major, | ||||
&src_minor, | &src_minor, | ||||
), | ), | ||||
MergePick::Minor => CopySource::new_from_merge( | MergePick::Minor => CopySource::new_from_merge( | ||||
current_merge, | current_merge, | ||||
for (dest, src_major) in major { | for (dest, src_major) in major { | ||||
let src_minor = minor.get(&dest); | let src_minor = minor.get(&dest); | ||||
match src_minor { | match src_minor { | ||||
None => { | None => { | ||||
minor.insert(dest, src_major); | minor.insert(dest, src_major); | ||||
} | } | ||||
Some(src_minor) => { | Some(src_minor) => { | ||||
let (pick, overwrite) = | let (pick, overwrite) = | ||||
cmp_value(oracle, &dest, src_minor, &src_major); | cmp_value(&dest, src_minor, &src_major); | ||||
if overwrite { | if overwrite { | ||||
oracle.record_overwrite(src_minor.rev, current_merge); | |||||
oracle.record_overwrite(src_major.rev, current_merge); | |||||
let src = match pick { | let src = match pick { | ||||
MergePick::Major => CopySource::new_from_merge( | MergePick::Major => CopySource::new_from_merge( | ||||
current_merge, | current_merge, | ||||
&src_major, | &src_major, | ||||
src_minor, | src_minor, | ||||
), | ), | ||||
MergePick::Minor => CopySource::new_from_merge( | MergePick::Minor => CopySource::new_from_merge( | ||||
current_merge, | current_merge, | ||||
for d in minor.diff(&major) { | for d in minor.diff(&major) { | ||||
match d { | match d { | ||||
DiffItem::Add(k, v) => to_minor(k, v), | DiffItem::Add(k, v) => to_minor(k, v), | ||||
DiffItem::Remove(k, v) => to_major(k, v), | DiffItem::Remove(k, v) => to_major(k, v), | ||||
DiffItem::Update { old, new } => { | DiffItem::Update { old, new } => { | ||||
let (dest, src_major) = new; | let (dest, src_major) = new; | ||||
let (_, src_minor) = old; | let (_, src_minor) = old; | ||||
let (pick, overwrite) = | let (pick, overwrite) = | ||||
cmp_value(oracle, dest, src_minor, src_major); | cmp_value(dest, src_minor, src_major); | ||||
if overwrite { | if overwrite { | ||||
oracle.record_overwrite(src_minor.rev, current_merge); | |||||
oracle.record_overwrite(src_major.rev, current_merge); | |||||
let src = match pick { | let src = match pick { | ||||
MergePick::Major => CopySource::new_from_merge( | MergePick::Major => CopySource::new_from_merge( | ||||
current_merge, | current_merge, | ||||
src_major, | src_major, | ||||
src_minor, | src_minor, | ||||
), | ), | ||||
MergePick::Minor => CopySource::new_from_merge( | MergePick::Minor => CopySource::new_from_merge( | ||||
current_merge, | current_merge, | ||||
/// The "minor" (p2) side prevails | /// The "minor" (p2) side prevails | ||||
Minor, | Minor, | ||||
/// Any side could be used (because they are the same) | /// Any side could be used (because they are the same) | ||||
Any, | Any, | ||||
} | } | ||||
/// decide which side prevails in case of conflicting values | /// decide which side prevails in case of conflicting values | ||||
#[allow(clippy::if_same_then_else)] | #[allow(clippy::if_same_then_else)] | ||||
fn compare_value<A: Fn(Revision, Revision) -> bool>( | fn compare_value( | ||||
path_map: &TwoWayPathMap, | path_map: &TwoWayPathMap, | ||||
current_merge: Revision, | current_merge: Revision, | ||||
changes: &ChangedFiles, | changes: &ChangedFiles, | ||||
oracle: &mut AncestorOracle<A>, | |||||
dest: &PathToken, | dest: &PathToken, | ||||
src_minor: &CopySource, | src_minor: &CopySource, | ||||
src_major: &CopySource, | src_major: &CopySource, | ||||
) -> (MergePick, bool) { | ) -> (MergePick, bool) { | ||||
if src_major.rev == current_merge { | if src_major.rev == current_merge { | ||||
if src_minor.rev == current_merge { | if src_minor.rev == current_merge { | ||||
if src_major.path.is_none() { | if src_major.path.is_none() { | ||||
// We cannot get different copy information for both p1 and p2 | // We cannot get different copy information for both p1 and p2 |
use cpython::ObjectProtocol; | use cpython::ObjectProtocol; | ||||
use cpython::PyBool; | |||||
use cpython::PyBytes; | use cpython::PyBytes; | ||||
use cpython::PyDict; | use cpython::PyDict; | ||||
use cpython::PyList; | use cpython::PyList; | ||||
use cpython::PyModule; | use cpython::PyModule; | ||||
use cpython::PyObject; | use cpython::PyObject; | ||||
use cpython::PyResult; | use cpython::PyResult; | ||||
use cpython::PyTuple; | use cpython::PyTuple; | ||||
use cpython::Python; | use cpython::Python; | ||||
/// | /// | ||||
/// See mercurial/copies.py for details | /// See mercurial/copies.py for details | ||||
pub fn combine_changeset_copies_wrapper( | pub fn combine_changeset_copies_wrapper( | ||||
py: Python, | py: Python, | ||||
revs: PyList, | revs: PyList, | ||||
children_count: PyDict, | children_count: PyDict, | ||||
target_rev: Revision, | target_rev: Revision, | ||||
rev_info: PyObject, | rev_info: PyObject, | ||||
is_ancestor: PyObject, | |||||
) -> PyResult<PyDict> { | ) -> PyResult<PyDict> { | ||||
let revs: PyResult<_> = | let revs: PyResult<_> = | ||||
revs.iter(py).map(|r| Ok(r.extract(py)?)).collect(); | revs.iter(py).map(|r| Ok(r.extract(py)?)).collect(); | ||||
// Wrap the `is_ancestor` python callback as a Rust closure | |||||
// | |||||
// No errors are expected from the Python side, and they will should only | |||||
// happens in case of programing error or severe data corruption. Such | |||||
// errors will raise panic and the rust-cpython harness will turn them into | |||||
// Python exception. | |||||
let is_ancestor_wrap = |anc: Revision, desc: Revision| -> bool { | |||||
is_ancestor | |||||
.call(py, (anc, desc), None) | |||||
.expect( | |||||
"rust-copy-tracing: python call to `is_ancestor` \ | |||||
failed", | |||||
) | |||||
.cast_into::<PyBool>(py) | |||||
.expect( | |||||
"rust-copy-tracing: python call to `is_ancestor` \ | |||||
returned unexpected non-Bool value", | |||||
) | |||||
.is_true() | |||||
}; | |||||
// Wrap the `rev_info_maker` python callback as a Rust closure | // Wrap the `rev_info_maker` python callback as a Rust closure | ||||
// | // | ||||
// No errors are expected from the Python side, and they will should only | // No errors are expected from the Python side, and they will should only | ||||
// happens in case of programing error or severe data corruption. Such | // happens in case of programing error or severe data corruption. Such | ||||
// errors will raise panic and the rust-cpython harness will turn them into | // errors will raise panic and the rust-cpython harness will turn them into | ||||
// Python exception. | // Python exception. | ||||
let rev_info_maker: RevInfoMaker<PyBytes> = | let rev_info_maker: RevInfoMaker<PyBytes> = | ||||
Box::new(|rev: Revision, d: &mut DataHolder<PyBytes>| -> RevInfo { | Box::new(|rev: Revision, d: &mut DataHolder<PyBytes>| -> RevInfo { | ||||
.map(|(k, v)| Ok((k.extract(py)?, v.extract(py)?))) | .map(|(k, v)| Ok((k.extract(py)?, v.extract(py)?))) | ||||
.collect(); | .collect(); | ||||
let res = combine_changeset_copies( | let res = combine_changeset_copies( | ||||
revs?, | revs?, | ||||
children_count?, | children_count?, | ||||
target_rev, | target_rev, | ||||
rev_info_maker, | rev_info_maker, | ||||
&is_ancestor_wrap, | |||||
); | ); | ||||
let out = PyDict::new(py); | let out = PyDict::new(py); | ||||
for (dest, source) in res.into_iter() { | for (dest, source) in res.into_iter() { | ||||
out.set_item( | out.set_item( | ||||
py, | py, | ||||
PyBytes::new(py, &dest.into_vec()), | PyBytes::new(py, &dest.into_vec()), | ||||
PyBytes::new(py, &source.into_vec()), | PyBytes::new(py, &source.into_vec()), | ||||
)?; | )?; | ||||
py, | py, | ||||
"combine_changeset_copies", | "combine_changeset_copies", | ||||
py_fn!( | py_fn!( | ||||
py, | py, | ||||
combine_changeset_copies_wrapper( | combine_changeset_copies_wrapper( | ||||
revs: PyList, | revs: PyList, | ||||
children: PyDict, | children: PyDict, | ||||
target_rev: Revision, | target_rev: Revision, | ||||
rev_info: PyObject, | rev_info: PyObject | ||||
is_ancestor: PyObject | |||||
) | ) | ||||
), | ), | ||||
)?; | )?; | ||||
let sys = PyModule::import(py, "sys")?; | let sys = PyModule::import(py, "sys")?; | ||||
let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; | let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; | ||||
sys_modules.set_item(py, dotted_name, &m)?; | sys_modules.set_item(py, dotted_name, &m)?; | ||||
Ok(m) | Ok(m) | ||||
} | } |