Skip to content

Commit 40a028c

Browse files
bors[bot]Veykril
andauthored
Merge #6105
6105: Fix path comparison not comparing paths correctly with unequal lengths r=matklad a=Veykril ~~This PR includes the commit from #6102 there as I found a bug while writing that(so either merging this or both in order works) so I included a test there already which was just ignored.~~ This PR fixes that, basically inserting imports didn't consider path length for equality, so depending on the order it might insert the path before or after another import if they only differ in segment length. ~~Diff without the commit of #6102 2d90d3937d71f9a00f3d44c15b20679215311637~~ Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
2 parents c01cd6e + 95ea23c commit 40a028c

File tree

1 file changed

+85
-48
lines changed

1 file changed

+85
-48
lines changed

crates/assists/src/utils/insert_use.rs

Lines changed: 85 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ use std::{
44
iter::{self, successors},
55
};
66

7-
use ast::{
8-
edit::{AstNodeEdit, IndentLevel},
9-
PathSegmentKind, VisibilityOwner,
10-
};
7+
use itertools::{EitherOrBoth, Itertools};
118
use syntax::{
129
algo,
13-
ast::{self, make, AstNode},
10+
ast::{
11+
self,
12+
edit::{AstNodeEdit, IndentLevel},
13+
make, AstNode, PathSegmentKind, VisibilityOwner,
14+
},
1415
InsertPosition, SyntaxElement, SyntaxNode,
1516
};
1617

@@ -193,13 +194,13 @@ fn recursive_merge(
193194
false => None,
194195
})
195196
.collect::<Option<Vec<_>>>()?;
196-
use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path()));
197+
use_trees.sort_unstable_by(|a, b| path_cmp_for_sort(a.path(), b.path()));
197198
for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
198199
if !merge.is_tree_allowed(&rhs_t) {
199200
return None;
200201
}
201202
let rhs_path = rhs_t.path();
202-
match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) {
203+
match use_trees.binary_search_by(|p| path_cmp_bin_search(p.path(), rhs_path.clone())) {
203204
Ok(idx) => {
204205
let lhs_t = &mut use_trees[idx];
205206
let lhs_path = lhs_t.path()?;
@@ -307,39 +308,77 @@ fn path_len(path: ast::Path) -> usize {
307308

308309
/// Orders paths in the following way:
309310
/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
310-
// FIXME: rustfmt sort lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
311+
// FIXME: rustfmt sorts lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
311312
// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
312313
// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
313-
fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering {
314-
match (path_is_self(a), path_is_self(b)) {
315-
(true, true) => Ordering::Equal,
316-
(true, false) => Ordering::Less,
317-
(false, true) => Ordering::Greater,
318-
(false, false) => {
319-
let a = segment_iter(a);
320-
let b = segment_iter(b);
321-
// cmp_by would be useful for us here but that is currently unstable
322-
// cmp doesnt work due the lifetimes on text's return type
323-
a.zip(b)
324-
.flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
325-
.find_map(|(a, b)| match a.text().cmp(b.text()) {
326-
ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord),
327-
Ordering::Equal => None,
328-
})
329-
.unwrap_or(Ordering::Equal)
330-
}
314+
fn path_cmp_for_sort(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
315+
match (a, b) {
316+
(None, None) => Ordering::Equal,
317+
(None, Some(_)) => Ordering::Less,
318+
(Some(_), None) => Ordering::Greater,
319+
(Some(ref a), Some(ref b)) => match (path_is_self(a), path_is_self(b)) {
320+
(true, true) => Ordering::Equal,
321+
(true, false) => Ordering::Less,
322+
(false, true) => Ordering::Greater,
323+
(false, false) => path_cmp_short(a, b),
324+
},
331325
}
332326
}
333327

334-
fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
335-
match (a, b) {
328+
/// Path comparison func for binary searching for merging.
329+
fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<ast::Path>) -> Ordering {
330+
match (lhs, rhs) {
336331
(None, None) => Ordering::Equal,
337332
(None, Some(_)) => Ordering::Less,
338333
(Some(_), None) => Ordering::Greater,
339-
(Some(a), Some(b)) => path_cmp(&a, &b),
334+
(Some(ref a), Some(ref b)) => path_cmp_short(a, b),
340335
}
341336
}
342337

338+
/// Short circuiting comparison, if both paths are equal until one of them ends they are considered
339+
/// equal
340+
fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering {
341+
let a = segment_iter(a);
342+
let b = segment_iter(b);
343+
// cmp_by would be useful for us here but that is currently unstable
344+
// cmp doesnt work due the lifetimes on text's return type
345+
a.zip(b)
346+
.find_map(|(a, b)| match path_segment_cmp(&a, &b) {
347+
Ordering::Equal => None,
348+
ord => Some(ord),
349+
})
350+
.unwrap_or(Ordering::Equal)
351+
}
352+
353+
/// Compares to paths, if one ends earlier than the other the has_tl parameters decide which is
354+
/// greater as a a path that has a tree list should be greater, while one that just ends without
355+
/// a tree list should be considered less.
356+
fn use_tree_path_cmp(a: &ast::Path, a_has_tl: bool, b: &ast::Path, b_has_tl: bool) -> Ordering {
357+
let a_segments = segment_iter(a);
358+
let b_segments = segment_iter(b);
359+
// cmp_by would be useful for us here but that is currently unstable
360+
// cmp doesnt work due the lifetimes on text's return type
361+
a_segments
362+
.zip_longest(b_segments)
363+
.find_map(|zipped| match zipped {
364+
EitherOrBoth::Both(ref a, ref b) => match path_segment_cmp(a, b) {
365+
Ordering::Equal => None,
366+
ord => Some(ord),
367+
},
368+
EitherOrBoth::Left(_) if !b_has_tl => Some(Ordering::Greater),
369+
EitherOrBoth::Left(_) => Some(Ordering::Less),
370+
EitherOrBoth::Right(_) if !a_has_tl => Some(Ordering::Less),
371+
EitherOrBoth::Right(_) => Some(Ordering::Greater),
372+
})
373+
.unwrap_or(Ordering::Equal)
374+
}
375+
376+
fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
377+
let a = a.name_ref();
378+
let b = b.name_ref();
379+
a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
380+
}
381+
343382
/// What type of merges are allowed.
344383
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
345384
pub enum MergeBehaviour {
@@ -389,7 +428,6 @@ impl ImportGroup {
389428
PathSegmentKind::Name(name) => match name.text().as_str() {
390429
"std" => ImportGroup::Std,
391430
"core" => ImportGroup::Std,
392-
// FIXME: can be ThisModule as well
393431
_ => ImportGroup::ExternCrate,
394432
},
395433
PathSegmentKind::Type { .. } => unreachable!(),
@@ -415,30 +453,30 @@ fn find_insert_position(
415453
.as_syntax_node()
416454
.children()
417455
.filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
418-
.flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node)));
456+
.flat_map(|(use_, node)| {
457+
let tree = use_.use_tree()?;
458+
let path = tree.path()?;
459+
let has_tl = tree.use_tree_list().is_some();
460+
Some((path, has_tl, node))
461+
});
419462
// Iterator that discards anything thats not in the required grouping
420463
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits
421464
let group_iter = path_node_iter
422465
.clone()
423-
.skip_while(|(path, _)| ImportGroup::new(path) != group)
424-
.take_while(|(path, _)| ImportGroup::new(path) == group);
466+
.skip_while(|(path, ..)| ImportGroup::new(path) != group)
467+
.take_while(|(path, ..)| ImportGroup::new(path) == group);
425468

426-
let segments = segment_iter(&insert_path);
427469
// 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
428470
let mut last = None;
429471
// find the element that would come directly after our new import
430-
let post_insert =
431-
group_iter.inspect(|(_, node)| last = Some(node.clone())).find(|(path, _)| {
432-
let check_segments = segment_iter(&path);
433-
segments
434-
.clone()
435-
.zip(check_segments)
436-
.flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
437-
.all(|(l, r)| l.text() <= r.text())
438-
});
472+
let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find(
473+
|&(ref path, has_tl, _)| {
474+
use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
475+
},
476+
);
439477
match post_insert {
440478
// insert our import before that element
441-
Some((_, node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
479+
Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
442480
// there is no element after our new import, so append it to the end of the group
443481
None => match last {
444482
Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
@@ -448,10 +486,10 @@ fn find_insert_position(
448486
let mut last = None;
449487
// find the group that comes after where we want to insert
450488
let post_group = path_node_iter
451-
.inspect(|(_, node)| last = Some(node.clone()))
452-
.find(|(p, _)| ImportGroup::new(p) > group);
489+
.inspect(|(.., node)| last = Some(node.clone()))
490+
.find(|(p, ..)| ImportGroup::new(p) > group);
453491
match post_group {
454-
Some((_, node)) => {
492+
Some((.., node)) => {
455493
(InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
456494
}
457495
// there is no such group, so append after the last one
@@ -844,7 +882,6 @@ use foo::bar::baz::Qux;",
844882
}
845883

846884
#[test]
847-
#[ignore] // FIXME: Order changes when switching lhs and rhs
848885
fn skip_merge_last_too_long2() {
849886
check_last(
850887
"foo::bar::baz::Qux",

0 commit comments

Comments
 (0)