Fix path comparison not comparing paths correctly with unequal lengths
This commit is contained in:
parent
3f4e9914ff
commit
95ea23cdef
@ -4,13 +4,14 @@ use std::{
|
|||||||
iter::{self, successors},
|
iter::{self, successors},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ast::{
|
use itertools::{EitherOrBoth, Itertools};
|
||||||
edit::{AstNodeEdit, IndentLevel},
|
|
||||||
PathSegmentKind, VisibilityOwner,
|
|
||||||
};
|
|
||||||
use syntax::{
|
use syntax::{
|
||||||
algo,
|
algo,
|
||||||
ast::{self, make, AstNode},
|
ast::{
|
||||||
|
self,
|
||||||
|
edit::{AstNodeEdit, IndentLevel},
|
||||||
|
make, AstNode, PathSegmentKind, VisibilityOwner,
|
||||||
|
},
|
||||||
InsertPosition, SyntaxElement, SyntaxNode,
|
InsertPosition, SyntaxElement, SyntaxNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -193,13 +194,13 @@ fn recursive_merge(
|
|||||||
false => None,
|
false => None,
|
||||||
})
|
})
|
||||||
.collect::<Option<Vec<_>>>()?;
|
.collect::<Option<Vec<_>>>()?;
|
||||||
use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path()));
|
use_trees.sort_unstable_by(|a, b| path_cmp_for_sort(a.path(), b.path()));
|
||||||
for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
|
for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
|
||||||
if !merge.is_tree_allowed(&rhs_t) {
|
if !merge.is_tree_allowed(&rhs_t) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let rhs_path = rhs_t.path();
|
let rhs_path = rhs_t.path();
|
||||||
match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) {
|
match use_trees.binary_search_by(|p| path_cmp_bin_search(p.path(), rhs_path.clone())) {
|
||||||
Ok(idx) => {
|
Ok(idx) => {
|
||||||
let lhs_t = &mut use_trees[idx];
|
let lhs_t = &mut use_trees[idx];
|
||||||
let lhs_path = lhs_t.path()?;
|
let lhs_path = lhs_t.path()?;
|
||||||
@ -307,39 +308,77 @@ fn path_len(path: ast::Path) -> usize {
|
|||||||
|
|
||||||
/// Orders paths in the following way:
|
/// Orders paths in the following way:
|
||||||
/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
|
/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
|
||||||
// FIXME: rustfmt sort lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
|
// FIXME: rustfmt sorts lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
|
||||||
// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
|
// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
|
||||||
// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
|
// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
|
||||||
fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering {
|
fn path_cmp_for_sort(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
|
||||||
match (path_is_self(a), path_is_self(b)) {
|
|
||||||
(true, true) => Ordering::Equal,
|
|
||||||
(true, false) => Ordering::Less,
|
|
||||||
(false, true) => Ordering::Greater,
|
|
||||||
(false, false) => {
|
|
||||||
let a = segment_iter(a);
|
|
||||||
let b = segment_iter(b);
|
|
||||||
// cmp_by would be useful for us here but that is currently unstable
|
|
||||||
// cmp doesnt work due the lifetimes on text's return type
|
|
||||||
a.zip(b)
|
|
||||||
.flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
|
|
||||||
.find_map(|(a, b)| match a.text().cmp(b.text()) {
|
|
||||||
ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord),
|
|
||||||
Ordering::Equal => None,
|
|
||||||
})
|
|
||||||
.unwrap_or(Ordering::Equal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
|
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(None, None) => Ordering::Equal,
|
(None, None) => Ordering::Equal,
|
||||||
(None, Some(_)) => Ordering::Less,
|
(None, Some(_)) => Ordering::Less,
|
||||||
(Some(_), None) => Ordering::Greater,
|
(Some(_), None) => Ordering::Greater,
|
||||||
(Some(a), Some(b)) => path_cmp(&a, &b),
|
(Some(ref a), Some(ref b)) => match (path_is_self(a), path_is_self(b)) {
|
||||||
|
(true, true) => Ordering::Equal,
|
||||||
|
(true, false) => Ordering::Less,
|
||||||
|
(false, true) => Ordering::Greater,
|
||||||
|
(false, false) => path_cmp_short(a, b),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Path comparison func for binary searching for merging.
|
||||||
|
fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<ast::Path>) -> Ordering {
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(None, None) => Ordering::Equal,
|
||||||
|
(None, Some(_)) => Ordering::Less,
|
||||||
|
(Some(_), None) => Ordering::Greater,
|
||||||
|
(Some(ref a), Some(ref b)) => path_cmp_short(a, b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Short circuiting comparison, if both paths are equal until one of them ends they are considered
|
||||||
|
/// equal
|
||||||
|
fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering {
|
||||||
|
let a = segment_iter(a);
|
||||||
|
let b = segment_iter(b);
|
||||||
|
// cmp_by would be useful for us here but that is currently unstable
|
||||||
|
// cmp doesnt work due the lifetimes on text's return type
|
||||||
|
a.zip(b)
|
||||||
|
.find_map(|(a, b)| match path_segment_cmp(&a, &b) {
|
||||||
|
Ordering::Equal => None,
|
||||||
|
ord => Some(ord),
|
||||||
|
})
|
||||||
|
.unwrap_or(Ordering::Equal)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares to paths, if one ends earlier than the other the has_tl parameters decide which is
|
||||||
|
/// greater as a a path that has a tree list should be greater, while one that just ends without
|
||||||
|
/// a tree list should be considered less.
|
||||||
|
fn use_tree_path_cmp(a: &ast::Path, a_has_tl: bool, b: &ast::Path, b_has_tl: bool) -> Ordering {
|
||||||
|
let a_segments = segment_iter(a);
|
||||||
|
let b_segments = segment_iter(b);
|
||||||
|
// cmp_by would be useful for us here but that is currently unstable
|
||||||
|
// cmp doesnt work due the lifetimes on text's return type
|
||||||
|
a_segments
|
||||||
|
.zip_longest(b_segments)
|
||||||
|
.find_map(|zipped| match zipped {
|
||||||
|
EitherOrBoth::Both(ref a, ref b) => match path_segment_cmp(a, b) {
|
||||||
|
Ordering::Equal => None,
|
||||||
|
ord => Some(ord),
|
||||||
|
},
|
||||||
|
EitherOrBoth::Left(_) if !b_has_tl => Some(Ordering::Greater),
|
||||||
|
EitherOrBoth::Left(_) => Some(Ordering::Less),
|
||||||
|
EitherOrBoth::Right(_) if !a_has_tl => Some(Ordering::Less),
|
||||||
|
EitherOrBoth::Right(_) => Some(Ordering::Greater),
|
||||||
|
})
|
||||||
|
.unwrap_or(Ordering::Equal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
|
||||||
|
let a = a.name_ref();
|
||||||
|
let b = b.name_ref();
|
||||||
|
a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
|
||||||
|
}
|
||||||
|
|
||||||
/// What type of merges are allowed.
|
/// What type of merges are allowed.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MergeBehaviour {
|
pub enum MergeBehaviour {
|
||||||
@ -389,7 +428,6 @@ impl ImportGroup {
|
|||||||
PathSegmentKind::Name(name) => match name.text().as_str() {
|
PathSegmentKind::Name(name) => match name.text().as_str() {
|
||||||
"std" => ImportGroup::Std,
|
"std" => ImportGroup::Std,
|
||||||
"core" => ImportGroup::Std,
|
"core" => ImportGroup::Std,
|
||||||
// FIXME: can be ThisModule as well
|
|
||||||
_ => ImportGroup::ExternCrate,
|
_ => ImportGroup::ExternCrate,
|
||||||
},
|
},
|
||||||
PathSegmentKind::Type { .. } => unreachable!(),
|
PathSegmentKind::Type { .. } => unreachable!(),
|
||||||
@ -415,30 +453,30 @@ fn find_insert_position(
|
|||||||
.as_syntax_node()
|
.as_syntax_node()
|
||||||
.children()
|
.children()
|
||||||
.filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
|
.filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
|
||||||
.flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node)));
|
.flat_map(|(use_, node)| {
|
||||||
|
let tree = use_.use_tree()?;
|
||||||
|
let path = tree.path()?;
|
||||||
|
let has_tl = tree.use_tree_list().is_some();
|
||||||
|
Some((path, has_tl, node))
|
||||||
|
});
|
||||||
// Iterator that discards anything thats not in the required grouping
|
// Iterator that discards anything thats not in the required grouping
|
||||||
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits
|
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits
|
||||||
let group_iter = path_node_iter
|
let group_iter = path_node_iter
|
||||||
.clone()
|
.clone()
|
||||||
.skip_while(|(path, _)| ImportGroup::new(path) != group)
|
.skip_while(|(path, ..)| ImportGroup::new(path) != group)
|
||||||
.take_while(|(path, _)| ImportGroup::new(path) == group);
|
.take_while(|(path, ..)| ImportGroup::new(path) == group);
|
||||||
|
|
||||||
let segments = segment_iter(&insert_path);
|
|
||||||
// track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
|
// track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
// find the element that would come directly after our new import
|
// find the element that would come directly after our new import
|
||||||
let post_insert =
|
let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find(
|
||||||
group_iter.inspect(|(_, node)| last = Some(node.clone())).find(|(path, _)| {
|
|&(ref path, has_tl, _)| {
|
||||||
let check_segments = segment_iter(&path);
|
use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
|
||||||
segments
|
},
|
||||||
.clone()
|
);
|
||||||
.zip(check_segments)
|
|
||||||
.flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
|
|
||||||
.all(|(l, r)| l.text() <= r.text())
|
|
||||||
});
|
|
||||||
match post_insert {
|
match post_insert {
|
||||||
// insert our import before that element
|
// insert our import before that element
|
||||||
Some((_, node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
|
Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
|
||||||
// there is no element after our new import, so append it to the end of the group
|
// there is no element after our new import, so append it to the end of the group
|
||||||
None => match last {
|
None => match last {
|
||||||
Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
|
Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
|
||||||
@ -448,10 +486,10 @@ fn find_insert_position(
|
|||||||
let mut last = None;
|
let mut last = None;
|
||||||
// find the group that comes after where we want to insert
|
// find the group that comes after where we want to insert
|
||||||
let post_group = path_node_iter
|
let post_group = path_node_iter
|
||||||
.inspect(|(_, node)| last = Some(node.clone()))
|
.inspect(|(.., node)| last = Some(node.clone()))
|
||||||
.find(|(p, _)| ImportGroup::new(p) > group);
|
.find(|(p, ..)| ImportGroup::new(p) > group);
|
||||||
match post_group {
|
match post_group {
|
||||||
Some((_, node)) => {
|
Some((.., node)) => {
|
||||||
(InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
|
(InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
|
||||||
}
|
}
|
||||||
// there is no such group, so append after the last one
|
// there is no such group, so append after the last one
|
||||||
@ -844,7 +882,6 @@ use foo::bar::baz::Qux;",
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore] // FIXME: Order changes when switching lhs and rhs
|
|
||||||
fn skip_merge_last_too_long2() {
|
fn skip_merge_last_too_long2() {
|
||||||
check_last(
|
check_last(
|
||||||
"foo::bar::baz::Qux",
|
"foo::bar::baz::Qux",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user