1
Fork 0

Rollup merge of #123344 - pietroalbini:pa-unused-imports, r=Nilstrieb

Remove braces when fixing a nested use tree into a single item

[Back in 2019](https://github.com/rust-lang/rust/pull/56645) I added rustfix support for the `unused_imports` lint, to automatically remove them when running `cargo fix`. For the most part this worked great, but when removing all but one childs of a nested use tree it turned `use foo::{Unused, Used}` into `use foo::{Used}`. This is slightly annoying, because it then requires you to run `rustfmt` to get `use foo::Used`.

This PR automatically removes braces and the surrouding whitespace when all but one child of a nested use tree are unused. To get it done I had to add the span of the nested use tree to the AST, and refactor a bit the code I wrote back then.

A thing I noticed is, there doesn't seem to be any `//@ run-rustfix` test for fixing the `unused_imports` lint. I created a test in `tests/suggestions` (is that the right directory?) that for now tests just what I added in the PR. I can followup in a separate PR to add more tests for fixing `unused_lints`.

This PR is best reviewed commit-by-commit.
This commit is contained in:
Matthias Krüger 2024-05-08 23:33:24 +02:00 committed by GitHub
commit d30af5e168
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 194 additions and 60 deletions

View file

@ -560,7 +560,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> {
self.add_import(prefix, kind, use_tree.span, item, root_span, item.id, vis);
}
ast::UseTreeKind::Nested(ref items) => {
ast::UseTreeKind::Nested { ref items, .. } => {
// Ensure there is at most one `self` in the list
let self_spans = items
.iter()

View file

@ -128,7 +128,7 @@ impl<'a, 'b, 'tcx> UnusedImportCheckVisitor<'a, 'b, 'tcx> {
self.unused_import(self.base_id).add(id);
}
}
ast::UseTreeKind::Nested(ref items) => self.check_imports_as_underscore(items),
ast::UseTreeKind::Nested { ref items, .. } => self.check_imports_as_underscore(items),
_ => {}
}
}
@ -254,7 +254,7 @@ impl<'a, 'b, 'tcx> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b, 'tcx> {
return;
}
if let ast::UseTreeKind::Nested(ref items) = use_tree.kind {
if let ast::UseTreeKind::Nested { ref items, .. } = use_tree.kind {
if items.is_empty() {
self.unused_import(self.base_id).add(id);
}
@ -268,9 +268,8 @@ impl<'a, 'b, 'tcx> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b, 'tcx> {
enum UnusedSpanResult {
Used,
FlatUnused(Span, Span),
NestedFullUnused(Vec<Span>, Span),
NestedPartialUnused(Vec<Span>, Vec<Span>),
Unused { spans: Vec<Span>, remove: Span },
PartialUnused { spans: Vec<Span>, remove: Vec<Span> },
}
fn calc_unused_spans(
@ -288,36 +287,33 @@ fn calc_unused_spans(
match use_tree.kind {
ast::UseTreeKind::Simple(..) | ast::UseTreeKind::Glob => {
if unused_import.unused.contains(&use_tree_id) {
UnusedSpanResult::FlatUnused(use_tree.span, full_span)
UnusedSpanResult::Unused { spans: vec![use_tree.span], remove: full_span }
} else {
UnusedSpanResult::Used
}
}
ast::UseTreeKind::Nested(ref nested) => {
ast::UseTreeKind::Nested { items: ref nested, span: tree_span } => {
if nested.is_empty() {
return UnusedSpanResult::FlatUnused(use_tree.span, full_span);
return UnusedSpanResult::Unused { spans: vec![use_tree.span], remove: full_span };
}
let mut unused_spans = Vec::new();
let mut to_remove = Vec::new();
let mut all_nested_unused = true;
let mut used_childs = 0;
let mut contains_self = false;
let mut previous_unused = false;
for (pos, (use_tree, use_tree_id)) in nested.iter().enumerate() {
let remove = match calc_unused_spans(unused_import, use_tree, *use_tree_id) {
UnusedSpanResult::Used => {
all_nested_unused = false;
used_childs += 1;
None
}
UnusedSpanResult::FlatUnused(span, remove) => {
unused_spans.push(span);
Some(remove)
}
UnusedSpanResult::NestedFullUnused(mut spans, remove) => {
UnusedSpanResult::Unused { mut spans, remove } => {
unused_spans.append(&mut spans);
Some(remove)
}
UnusedSpanResult::NestedPartialUnused(mut spans, mut to_remove_extra) => {
all_nested_unused = false;
UnusedSpanResult::PartialUnused { mut spans, remove: mut to_remove_extra } => {
used_childs += 1;
unused_spans.append(&mut spans);
to_remove.append(&mut to_remove_extra);
None
@ -326,7 +322,7 @@ fn calc_unused_spans(
if let Some(remove) = remove {
let remove_span = if nested.len() == 1 {
remove
} else if pos == nested.len() - 1 || !all_nested_unused {
} else if pos == nested.len() - 1 || used_childs > 0 {
// Delete everything from the end of the last import, to delete the
// previous comma
nested[pos - 1].0.span.shrink_to_hi().to(use_tree.span)
@ -344,14 +340,38 @@ fn calc_unused_spans(
to_remove.push(remove_span);
}
}
contains_self |= use_tree.prefix == kw::SelfLower
&& matches!(use_tree.kind, ast::UseTreeKind::Simple(None));
previous_unused = remove.is_some();
}
if unused_spans.is_empty() {
UnusedSpanResult::Used
} else if all_nested_unused {
UnusedSpanResult::NestedFullUnused(unused_spans, full_span)
} else if used_childs == 0 {
UnusedSpanResult::Unused { spans: unused_spans, remove: full_span }
} else {
UnusedSpanResult::NestedPartialUnused(unused_spans, to_remove)
// If there is only one remaining child that is used, the braces around the use
// tree are not needed anymore. In that case, we determine the span of the left
// brace and the right brace, and tell rustfix to remove them as well.
//
// This means that `use a::{B, C};` will be turned into `use a::B;` rather than
// `use a::{B};`, removing a rustfmt roundtrip.
//
// Note that we cannot remove the braces if the only item inside the use tree is
// `self`: `use foo::{self};` is valid Rust syntax, while `use foo::self;` errors
// out. We also cannot turn `use foo::{self}` into `use foo`, as the former doesn't
// import types with the same name as the module.
if used_childs == 1 && !contains_self {
// Left brace, from the start of the nested group to the first item.
to_remove.push(
tree_span.shrink_to_lo().to(nested.first().unwrap().0.span.shrink_to_lo()),
);
// Right brace, from the end of the last item to the end of the nested group.
to_remove.push(
nested.last().unwrap().0.span.shrink_to_hi().to(tree_span.shrink_to_hi()),
);
}
UnusedSpanResult::PartialUnused { spans: unused_spans, remove: to_remove }
}
}
}
@ -417,15 +437,11 @@ impl Resolver<'_, '_> {
let mut fixes = Vec::new();
let spans = match calc_unused_spans(unused, &unused.use_tree, unused.use_tree_id) {
UnusedSpanResult::Used => continue,
UnusedSpanResult::FlatUnused(span, remove) => {
fixes.push((remove, String::new()));
vec![span]
}
UnusedSpanResult::NestedFullUnused(spans, remove) => {
UnusedSpanResult::Unused { spans, remove } => {
fixes.push((remove, String::new()));
spans
}
UnusedSpanResult::NestedPartialUnused(spans, remove) => {
UnusedSpanResult::PartialUnused { spans, remove } => {
for fix in &remove {
fixes.push((*fix, String::new()));
}

View file

@ -2347,8 +2347,8 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
None => {}
}
}
} else if let UseTreeKind::Nested(use_trees) = &use_tree.kind {
for (use_tree, _) in use_trees {
} else if let UseTreeKind::Nested { items, .. } = &use_tree.kind {
for (use_tree, _) in items {
self.future_proof_import(use_tree);
}
}
@ -2525,7 +2525,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
ItemKind::Use(ref use_tree) => {
let maybe_exported = match use_tree.kind {
UseTreeKind::Simple(_) | UseTreeKind::Glob => MaybeExported::Ok(item.id),
UseTreeKind::Nested(_) => MaybeExported::NestedUse(&item.vis),
UseTreeKind::Nested { .. } => MaybeExported::NestedUse(&item.vis),
};
self.resolve_doc_links(&item.attrs, maybe_exported);