fix(TreeBuilder): resolve not-really-conflicts (left and right agree), take 2 #204
2 changed files with 100 additions and 24 deletions
|
@ -513,4 +513,49 @@ mod tests {
|
|||
let _pretty_printed = merged_tree.pretty_print(&class_mapping, &DisplaySettings::default());
|
||||
// assert_eq!(pretty_printed, "{}"); // TODO there should be a delete/modify conflict here!
|
||||
}
|
||||
|
||||
fn rust_matchers() -> (TreeMatcher<'static>, TreeMatcher<'static>) {
|
||||
let lang_profile = LangProfile::detect_from_filename("test.rs").unwrap();
|
||||
let primary_matcher = TreeMatcher {
|
||||
min_height: 0,
|
||||
sim_threshold: 0.5,
|
||||
max_recovery_size: 100,
|
||||
use_rted: true,
|
||||
lang_profile,
|
||||
};
|
||||
let auxiliary_matcher = TreeMatcher {
|
||||
min_height: 1,
|
||||
sim_threshold: 0.5,
|
||||
max_recovery_size: 100,
|
||||
use_rted: false,
|
||||
lang_profile,
|
||||
};
|
||||
(primary_matcher, auxiliary_matcher)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_insert_not_really_a_conflict() {
|
||||
let ctx = ctx();
|
||||
|
||||
// both `left` and `right` add the `'s` to `&self`, so this would-be-conflict should be
|
||||
// resolved during the construction of the tree. NB: The `<'s>` is added just so that
|
||||
// `left` and `right` are not completely identical (which would've made the resolution trivial)
|
||||
let base = ctx.parse_rust("fn foo(&self) {}");
|
||||
let left = ctx.parse_rust("fn foo(&'s self) {}");
|
||||
let right = ctx.parse_rust("fn foo<'s>(&'s self) {}");
|
||||
|
||||
let (primary_matcher, auxiliary_matcher) = rust_matchers();
|
||||
let (merged_tree, class_mapping) = three_way_merge(
|
||||
&base,
|
||||
&left,
|
||||
&right,
|
||||
None,
|
||||
&primary_matcher,
|
||||
&auxiliary_matcher,
|
||||
None,
|
||||
);
|
||||
|
||||
let pretty_printed = merged_tree.pretty_print(&class_mapping, &DisplaySettings::default());
|
||||
assert_eq!(pretty_printed, "fn foo<'s>(&'s self) {}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,40 +227,71 @@ impl<'a, 'b> TreeBuilder<'a, 'b> {
|
|||
cursor = children_map.get(&predecessor);
|
||||
}
|
||||
2 => {
|
||||
let conflict = self.build_conflict(
|
||||
let Ok((next_cursor, conflict)) = self.build_conflict(
|
||||
predecessor,
|
||||
children_map,
|
||||
base_children_map,
|
||||
&mut seen_nodes,
|
||||
visiting_state,
|
||||
);
|
||||
match conflict {
|
||||
Err(_) => {
|
||||
let line_based =
|
||||
self.commutative_or_line_based_local_fallback(node, visiting_state);
|
||||
return line_based;
|
||||
}
|
||||
Ok((next_cursor, conflict)) => {
|
||||
if let PCSNode::Node { node: leader, .. } = node {
|
||||
if let Some(commutative_parent) = self
|
||||
.lang_profile
|
||||
.get_commutative_parent(leader.grammar_name())
|
||||
{
|
||||
let solved_conflict = self.resolve_commutative_conflict(
|
||||
conflict,
|
||||
commutative_parent,
|
||||
visiting_state,
|
||||
)?;
|
||||
children.extend(solved_conflict);
|
||||
} else {
|
||||
children.push(conflict);
|
||||
}
|
||||
) else {
|
||||
let line_based =
|
||||
self.commutative_or_line_based_local_fallback(node, visiting_state);
|
||||
return line_based;
|
||||
};
|
||||
|
||||
let MergedTree::Conflict {
|
||||
|
||||
ref left,
|
||||
ref right,
|
||||
..
|
||||
} = conflict
|
||||
else {
|
||||
unreachable!("`build_conflict` should return a conflict")
|
||||
};
|
||||
|
||||
if left.len() == right.len()
|
||||
&& std::iter::zip(left, right).all(|(l, r)| l.isomorphic_to(r))
|
||||
{
|
||||
// both sides of the "conflict" consist of nodes that are pairwise isomorphic.
|
||||
// This means that both sides actually agree in how they change the base.
|
||||
// Therefore, we resolve the conflict trivially by picking the left side (WLOG)
|
||||
let not_really_a_conflict: Vec<_> = left
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|l| {
|
||||
MergedTree::new_exact(
|
||||
self.class_mapping
|
||||
.map_to_leader(RevNode::new(Revision::Left, l)),
|
||||
RevisionNESet::singleton(Revision::Left).with(Revision::Right),
|
||||
self.class_mapping,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
children.extend(not_really_a_conflict);
|
||||
} else {
|
||||
// reason: the following two `if`s should really be `&&`-ed,
|
||||
// but https://github.com/rust-lang/rust/issues/53667
|
||||
// until then, collapsing one but not the other looks misleading
|
||||
#[allow(clippy::collapsible_if)]
|
||||
if let PCSNode::Node { node: leader, .. } = node {
|
||||
if let Some(commutative_parent) = self
|
||||
.lang_profile
|
||||
.get_commutative_parent(leader.grammar_name())
|
||||
{
|
||||
let solved_conflict = self.resolve_commutative_conflict(
|
||||
conflict,
|
||||
commutative_parent,
|
||||
visiting_state,
|
||||
)?;
|
||||
children.extend(solved_conflict);
|
||||
} else {
|
||||
children.push(conflict);
|
||||
}
|
||||
cursor = next_cursor;
|
||||
} else {
|
||||
children.push(conflict);
|
||||
}
|
||||
}
|
||||
cursor = next_cursor;
|
||||
}
|
||||
_ => unreachable!("unexpected conflict size: more than two diverging sides!"),
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue
Since we're destructuring
conflict
here anyway, we could just pass the sides toresolve_commutative_conflict
directly. And since destructingconflict
is all the latter does besides callingcommutatively_merge_list
, I think we could just removeresolve_commutative_conflict
and callcommutatively_merge_list
directlyIt sounds good to me, intuitively
Great, I'll open a PR for that then