Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 104 additions & 4 deletions src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ fn module_prefix(path: &[UseSegment]) -> &[UseSegment] {
&path[..(path.len() - 1).max(1)]
}

/// Rewrites a path ending in an aliased `self` (e.g., flattened from
/// `use a::{self as b};`) to the equivalent `a as b`. Used for One-level
/// imports_granularity.
fn collapse_trailing_self_alias(path: &mut Vec<UseSegment>) {
if path.len() < 2 {
return;
}

let alias = match &path.last().unwrap().kind {
UseSegmentKind::Slf(Some(alias)) => alias.clone(),
_ => return,
};
let prev = path.len() - 2;
match &mut path[prev].kind {
UseSegmentKind::Ident(_, a @ None)
| UseSegmentKind::Super(a @ None)
| UseSegmentKind::Crate(a @ None) => *a = Some(alias),
_ => return,
}

path.pop();
}

impl<'a> FmtVisitor<'a> {
pub(crate) fn format_import(&mut self, item: &ast::Item, tree: &ast::UseTree) {
let span = item.span();
Expand Down Expand Up @@ -231,6 +254,30 @@ pub(crate) fn normalize_use_trees_with_granularity(
ImportGranularity::One => SharedPrefix::One,
};

let mut result = merge_use_trees(use_trees, import_granularity, merge_by);
if merge_by == SharedPrefix::One {
// The result of merging can depend on the order in which the trees
// were encountered, in which case a single pass is not stable
// (see #6027). Keep re-merging until we reach a fixed point so that
// formatting stays idempotent. This normally converges after one
// extra pass. The cap at four iterations is just a safeguard against
// oscillation.
for _ in 0..4 {
let next = merge_use_trees(result.clone(), import_granularity, merge_by);
if next == result {
break;
}
result = next;
}
}
result
}

fn merge_use_trees(
use_trees: Vec<UseTree>,
import_granularity: ImportGranularity,
merge_by: SharedPrefix,
) -> Vec<UseTree> {
let mut result = Vec::with_capacity(use_trees.len());
for use_tree in use_trees {
if use_tree.contains_comment() || use_tree.attrs.is_some() {
Expand Down Expand Up @@ -713,6 +760,12 @@ impl UseTree {
for flattened in &mut nested_use_tree.clone().flatten(import_granularity) {
let mut new_path = prefix.to_vec();
new_path.append(&mut flattened.path);
// `use a::{self as b};` is equivalent to `use a as b;`.
// Normalize to the latter so that merging treats both
// forms the same way.
if import_granularity == ImportGranularity::One {
collapse_trailing_self_alias(&mut new_path);
}
result.push(UseTree {
path: new_path,
span: self.span,
Expand Down Expand Up @@ -775,7 +828,14 @@ fn merge_rest(
merge_by: SharedPrefix,
) -> Option<Vec<UseSegment>> {
if a.len() == len && b.len() == len {
return None;
if a[len - 1] == b[len - 1] {
return None;
}

// The paths only differ by the alias of the last segment, e.g.,
// `foo::bar` and `foo::bar as baz`. These are distinct imports, so
// keep both in a list: `foo::{bar, bar as baz}`.
len -= 1;
}
if a.len() != len && b.len() != len {
let style_edition = a[len].style_edition;
Expand Down Expand Up @@ -819,7 +879,9 @@ fn merge_rest(
_ => list.push(UseTree::from_path(rest.to_vec(), DUMMY_SP)),
}
return Some(vec![
b[0].clone(),
// The alias of the common segment (if any) has moved to the
// `self` item in the list; drop it from the root.
common.remove_alias(),
UseSegment {
kind: UseSegmentKind::List(list),
style_edition,
Expand Down Expand Up @@ -857,11 +919,24 @@ fn merge_use_trees_inner(trees: &mut Vec<UseTree>, use_tree: UseTree, merge_by:
// tree `use_tree` should be merge.
// In other cases `similarity` won't be used, so set it to `0` as a dummy value.
let similarity = if merge_by == SharedPrefix::One {
tree.path
let similarity = tree
.path
.iter()
.zip(&use_tree.path)
.take_while(|(a, b)| a.equal_except_alias(b))
.count()
.count();
// Single-segment trees that only differ by alias, e.g., `foo`
// and `foo as bar`, import distinct names that must not be
// merged into each other.
if similarity == 1
&& tree.path.len() == 1
&& use_tree.path.len() == 1
&& tree.path != use_tree.path
{
0
} else {
similarity
}
} else {
0
};
Expand Down Expand Up @@ -1431,6 +1506,31 @@ mod test {
["b", "a::ac::{aca, acb}", "a::{aa::*, ab}"],
["{a::{aa::*, ab, ac::{aca, acb}}, b}"]
);

// aliases should not be dropped when merging (#6027)
test_merge!(One, ["a::b", "a::b as c"], ["a::{b, b as c}"]);

test_merge!(One, ["a::b as c", "a::b"], ["a::{b as c, b}"]);

test_merge!(One, ["a::z", "a::b", "a::b as c"], ["a::{b, b as c, z}"]);

test_merge!(One, ["a", "a as b"], ["{a, a as b}"]);

test_merge!(One, ["a as b", "a as c"], ["{a as b, a as c}"]);

test_merge!(One, ["a::b as c", "a::b as d"], ["a::{b as c, b as d}"]);

// the alias of a shared root is rewritten as `self as alias`,
// regardless of which side of the merge it appears on
test_merge!(One, ["a as x", "a::b"], ["a::{self as x, b}"]);

test_merge!(One, ["a::b", "a as x"], ["a::{self as x, b}"]);

// an aliased root and a deeper path under the same root should merge
// to a stable result regardless of their order
test_merge!(One, ["a", "a as b", "a::c"], ["{a, a::{self as b, c}}"]);

test_merge!(One, ["a::c", "a as b", "a"], ["a::{self as b, self, c}"]);
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions tests/source/5131_one.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ use bar::d::e;
use bar::d::e as e2;
use qux::h;
use qux::i;
use qux::i as j;
10 changes: 10 additions & 0 deletions tests/source/issue-6027.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// rustfmt-imports_granularity: One

use qux::h;
use qux::i;
use qux::i as j;
use bar::a;
use bar::b::f;
use bar::b as B;
use baz::c as x;
use baz::c as y;
4 changes: 2 additions & 2 deletions tests/target/5131_one.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ pub use foo::{x, x as x2, y};
use {
bar::{
a,
b::{self, f, g},
b::{self, f, f as f2, g},
c,
d::{e, e as e2},
},
qux::{h, i},
qux::{h, i, i as j},
};
10 changes: 10 additions & 0 deletions tests/target/issue-6027.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// rustfmt-imports_granularity: One

use {
bar::{
a,
b::{self as B, f},
},
baz::{c as x, c as y},
qux::{h, i, i as j},
};