Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of rustfixable unused_imports lint #56645

Merged
merged 2 commits into from
Feb 11, 2019
Merged
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
10 changes: 10 additions & 0 deletions src/librustc/lint/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ pub enum BuiltinLintDiagnostics {
MacroExpandedMacroExportsAccessedByAbsolutePaths(Span),
ElidedLifetimesInPaths(usize, Span, bool, Span, String),
UnknownCrateTypes(Span, String, String),
UnusedImports(String, Vec<(Span, String)>),
}

impl BuiltinLintDiagnostics {
Expand Down Expand Up @@ -548,6 +549,15 @@ impl BuiltinLintDiagnostics {
BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => {
db.span_suggestion(span, &note, sugg, Applicability::MaybeIncorrect);
}
BuiltinLintDiagnostics::UnusedImports(message, replaces) => {
if !replaces.is_empty() {
db.multipart_suggestion(
&message,
replaces,
Applicability::MachineApplicable,
);
}
}
}
}
}
Expand Down
203 changes: 181 additions & 22 deletions src/librustc_resolve/check_unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,52 @@
//
// Unused trait imports can't be checked until the method resolution. We save
// candidates here, and do the actual check in librustc_typeck/check_unused.rs.
//
// Checking for unused imports is split into three steps:
//
// - `UnusedImportCheckVisitor` walks the AST to find all the unused imports
// inside of `UseTree`s, recording their `NodeId`s and grouping them by
// the parent `use` item
//
// - `calc_unused_spans` then walks over all the `use` items marked in the
// previous step to collect the spans associated with the `NodeId`s and to
// calculate the spans that can be removed by rustfix; This is done in a
// separate step to be able to collapse the adjacent spans that rustfix
// will remove
//
// - `check_crate` finally emits the diagnostics based on the data generated
// in the last step

use std::ops::{Deref, DerefMut};

use crate::Resolver;
use crate::resolve_imports::ImportDirectiveSubclass;

use rustc::{lint, ty};
use rustc::util::nodemap::NodeMap;
use rustc::{lint, ty};
use rustc_data_structures::fx::FxHashSet;
use syntax::ast;
use syntax::visit::{self, Visitor};
use syntax_pos::{Span, MultiSpan, DUMMY_SP};

struct UnusedImport<'a> {
use_tree: &'a ast::UseTree,
use_tree_id: ast::NodeId,
item_span: Span,
unused: FxHashSet<ast::NodeId>,
}

impl<'a> UnusedImport<'a> {
fn add(&mut self, id: ast::NodeId) {
self.unused.insert(id);
}
}

struct UnusedImportCheckVisitor<'a, 'b: 'a> {
resolver: &'a mut Resolver<'b>,
/// All the (so far) unused imports, grouped path list
unused_imports: NodeMap<NodeMap<Span>>,
unused_imports: NodeMap<UnusedImport<'a>>,
base_use_tree: Option<&'a ast::UseTree>,
base_id: ast::NodeId,
item_span: Span,
}
Expand All @@ -46,24 +75,39 @@ impl<'a, 'b> DerefMut for UnusedImportCheckVisitor<'a, 'b> {
impl<'a, 'b> UnusedImportCheckVisitor<'a, 'b> {
// We have information about whether `use` (import) directives are actually
// used now. If an import is not used at all, we signal a lint error.
fn check_import(&mut self, item_id: ast::NodeId, id: ast::NodeId, span: Span) {
fn check_import(&mut self, id: ast::NodeId) {
let mut used = false;
self.per_ns(|this, ns| used |= this.used_imports.contains(&(id, ns)));
if !used {
if self.maybe_unused_trait_imports.contains(&id) {
// Check later.
return;
}
self.unused_imports.entry(item_id).or_default().insert(id, span);
self.unused_import(self.base_id).add(id);
} else {
// This trait import is definitely used, in a way other than
// method resolution.
self.maybe_unused_trait_imports.remove(&id);
if let Some(i) = self.unused_imports.get_mut(&item_id) {
i.remove(&id);
if let Some(i) = self.unused_imports.get_mut(&self.base_id) {
i.unused.remove(&id);
}
}
}

fn unused_import(&mut self, id: ast::NodeId) -> &mut UnusedImport<'a> {
let use_tree_id = self.base_id;
let use_tree = self.base_use_tree.unwrap();
let item_span = self.item_span;

self.unused_imports
.entry(id)
.or_insert_with(|| UnusedImport {
use_tree,
use_tree_id,
item_span,
unused: FxHashSet::default(),
})
}
}

impl<'a, 'b> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b> {
Expand All @@ -88,31 +132,112 @@ impl<'a, 'b> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b> {
// This allows the grouping of all the lints in the same item
if !nested {
self.base_id = id;
self.base_use_tree = Some(use_tree);
}

if let ast::UseTreeKind::Nested(ref items) = use_tree.kind {
// If it's the parent group, cover the entire use item
let span = if nested {
use_tree.span
} else {
self.item_span
};

if items.is_empty() {
self.unused_imports
.entry(self.base_id)
.or_default()
.insert(id, span);
self.unused_import(self.base_id).add(id);
}
} else {
let base_id = self.base_id;
self.check_import(base_id, id, use_tree.span);
self.check_import(id);
}

visit::walk_use_tree(self, use_tree, id);
}
}

enum UnusedSpanResult {
Used,
FlatUnused(Span, Span),
NestedFullUnused(Vec<Span>, Span),
NestedPartialUnused(Vec<Span>, Vec<Span>),
}

fn calc_unused_spans(
unused_import: &UnusedImport<'_>,
use_tree: &ast::UseTree,
use_tree_id: ast::NodeId,
) -> UnusedSpanResult {
// The full span is the whole item's span if this current tree is not nested inside another
// This tells rustfix to remove the whole item if all the imports are unused
let full_span = if unused_import.use_tree.span == use_tree.span {
unused_import.item_span
} else {
use_tree.span
};
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)
} else {
UnusedSpanResult::Used
}
}
ast::UseTreeKind::Nested(ref nested) => {
if nested.len() == 0 {
return UnusedSpanResult::FlatUnused(use_tree.span, full_span);
}

let mut unused_spans = Vec::new();
let mut to_remove = Vec::new();
let mut all_nested_unused = true;
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;
None
}
UnusedSpanResult::FlatUnused(span, remove) => {
unused_spans.push(span);
Some(remove)
}
UnusedSpanResult::NestedFullUnused(mut spans, remove) => {
unused_spans.append(&mut spans);
Some(remove)
}
UnusedSpanResult::NestedPartialUnused(mut spans, mut to_remove_extra) => {
all_nested_unused = false;
unused_spans.append(&mut spans);
to_remove.append(&mut to_remove_extra);
None
}
};
if let Some(remove) = remove {
let remove_span = if nested.len() == 1 {
remove
} else if pos == nested.len() - 1 || !all_nested_unused {
// 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)
} else {
// Delete everything until the next import, to delete the trailing commas
use_tree.span.to(nested[pos + 1].0.span.shrink_to_lo())
};

// Try to collapse adjacent spans into a single one. This prevents all cases of
// overlapping removals, which are not supported by rustfix
if previous_unused && !to_remove.is_empty() {
let previous = to_remove.pop().unwrap();
to_remove.push(previous.to(remove_span));
} else {
to_remove.push(remove_span);
}
}
previous_unused = remove.is_some();
}
if unused_spans.is_empty() {
UnusedSpanResult::Used
} else if all_nested_unused {
UnusedSpanResult::NestedFullUnused(unused_spans, full_span)
} else {
UnusedSpanResult::NestedPartialUnused(unused_spans, to_remove)
}
}
}
}

pub fn check_crate(resolver: &mut Resolver<'_>, krate: &ast::Crate) {
for directive in resolver.potentially_unused_imports.iter() {
match directive.subclass {
Expand Down Expand Up @@ -152,14 +277,33 @@ pub fn check_crate(resolver: &mut Resolver<'_>, krate: &ast::Crate) {
let mut visitor = UnusedImportCheckVisitor {
resolver,
unused_imports: Default::default(),
base_use_tree: None,
base_id: ast::DUMMY_NODE_ID,
item_span: DUMMY_SP,
};
visit::walk_crate(&mut visitor, krate);

for (id, spans) in &visitor.unused_imports {
for unused in visitor.unused_imports.values() {
let mut fixes = Vec::new();
let mut 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) => {
fixes.push((remove, String::new()));
spans
}
UnusedSpanResult::NestedPartialUnused(spans, remove) => {
for fix in &remove {
fixes.push((*fix, String::new()));
}
spans
}
};

let len = spans.len();
let mut spans = spans.values().cloned().collect::<Vec<Span>>();
spans.sort();
let ms = MultiSpan::from_spans(spans.clone());
let mut span_snippets = spans.iter()
Expand All @@ -177,6 +321,21 @@ pub fn check_crate(resolver: &mut Resolver<'_>, krate: &ast::Crate) {
} else {
String::new()
});
visitor.session.buffer_lint(lint::builtin::UNUSED_IMPORTS, *id, ms, &msg);

let fix_msg = if fixes.len() == 1 && fixes[0].0 == unused.item_span {
"remove the whole `use` item"
} else if spans.len() > 1 {
"remove the unused imports"
} else {
"remove the unused import"
};

visitor.session.buffer_lint_with_diagnostic(
lint::builtin::UNUSED_IMPORTS,
unused.use_tree_id,
ms,
&msg,
lint::builtin::BuiltinLintDiagnostics::UnusedImports(fix_msg.into(), fixes),
);
}
}
2 changes: 1 addition & 1 deletion src/test/ui/bad/bad-lint-cap2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error: unused import: `std::option`
--> $DIR/bad-lint-cap2.rs:6:5
|
LL | use std::option; //~ ERROR
| ^^^^^^^^^^^
| ----^^^^^^^^^^^- help: remove the whole `use` item
|
note: lint level defined here
--> $DIR/bad-lint-cap2.rs:4:9
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/bad/bad-lint-cap3.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ warning: unused import: `std::option`
--> $DIR/bad-lint-cap3.rs:7:5
|
LL | use std::option; //~ WARN
| ^^^^^^^^^^^
| ----^^^^^^^^^^^- help: remove the whole `use` item
|
note: lint level defined here
--> $DIR/bad-lint-cap3.rs:4:9
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/imports/unused.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error: unused import: `super::f`
--> $DIR/unused.rs:7:24
|
LL | pub(super) use super::f; //~ ERROR unused
| ^^^^^^^^
| ---------------^^^^^^^^- help: remove the whole `use` item
|
note: lint level defined here
--> $DIR/unused.rs:1:9
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/issues/issue-30730.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error: unused import: `std::thread`
--> $DIR/issue-30730.rs:3:5
|
LL | use std::thread;
| ^^^^^^^^^^^
| ----^^^^^^^^^^^- help: remove the whole `use` item
|
note: lint level defined here
--> $DIR/issue-30730.rs:2:9
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error: unused import: `a::x`
--> $DIR/lint-directives-on-use-items-issue-10534.rs:12:9
|
LL | use a::x; //~ ERROR: unused import
| ^^^^
| ----^^^^- help: remove the whole `use` item
|
note: lint level defined here
--> $DIR/lint-directives-on-use-items-issue-10534.rs:1:9
Expand All @@ -14,7 +14,7 @@ error: unused import: `a::y`
--> $DIR/lint-directives-on-use-items-issue-10534.rs:21:9
|
LL | use a::y; //~ ERROR: unused import
| ^^^^
| ----^^^^- help: remove the whole `use` item
|
note: lint level defined here
--> $DIR/lint-directives-on-use-items-issue-10534.rs:20:12
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/lint/lint-unused-imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bar::c::cc as cal;
use std::mem::*; // shouldn't get errors for not using
// everything imported
use std::fmt::{};
//~^ ERROR unused import: `use std::fmt::{};`
//~^ ERROR unused import: `std::fmt::{}`

// Should get errors for both 'Some' and 'None'
use std::option::Option::{Some, None};
Expand Down
Loading