Skip to content

Commit a820d99

Browse files
committed
Group unused import warnings per path list
Given a file ```rust use std::collections::{BinaryHeap, BTreeMap, BTreeSet}; fn main() {} ``` Show a single warning, instead of three for each unused import: ```nocode warning: unused imports, #[warn(unused_imports)] on by default --> foo.rs:1:24 | 1 | use std::collections::{BinaryHeap, BTreeMap, BTreeSet}; | ^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ``` Include support for lints pointing at `MultilineSpan`s, instead of just `Span`s.
1 parent 0491a23 commit a820d99

File tree

7 files changed

+82
-26
lines changed

7 files changed

+82
-26
lines changed

src/librustc/lint/context.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ pub trait IntoEarlyLint {
106106
fn into_early_lint(self, id: LintId) -> EarlyLint;
107107
}
108108

109-
impl<'a> IntoEarlyLint for (Span, &'a str) {
109+
impl<'a, S: Into<MultiSpan>> IntoEarlyLint for (S, &'a str) {
110110
fn into_early_lint(self, id: LintId) -> EarlyLint {
111111
let (span, msg) = self;
112112
let mut diagnostic = Diagnostic::new(errors::Level::Warning, msg);
@@ -530,7 +530,10 @@ pub trait LintContext: Sized {
530530
})
531531
}
532532

533-
fn lookup_and_emit(&self, lint: &'static Lint, span: Option<Span>, msg: &str) {
533+
fn lookup_and_emit<S: Into<MultiSpan>>(&self,
534+
lint: &'static Lint,
535+
span: Option<S>,
536+
msg: &str) {
534537
let (level, src) = match self.level_src(lint) {
535538
None => return,
536539
Some(pair) => pair,
@@ -553,7 +556,7 @@ pub trait LintContext: Sized {
553556
}
554557

555558
/// Emit a lint at the appropriate level, for a particular span.
556-
fn span_lint(&self, lint: &'static Lint, span: Span, msg: &str) {
559+
fn span_lint<S: Into<MultiSpan>>(&self, lint: &'static Lint, span: S, msg: &str) {
557560
self.lookup_and_emit(lint, Some(span), msg);
558561
}
559562

@@ -601,7 +604,7 @@ pub trait LintContext: Sized {
601604

602605
/// Emit a lint at the appropriate level, with no associated span.
603606
fn lint(&self, lint: &'static Lint, msg: &str) {
604-
self.lookup_and_emit(lint, None, msg);
607+
self.lookup_and_emit(lint, None as Option<Span>, msg);
605608
}
606609

607610
/// Merge the lints specified by any lint attributes into the

src/librustc/session/mod.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,15 @@ impl Session {
258258
pub fn unimpl(&self, msg: &str) -> ! {
259259
self.diagnostic().unimpl(msg)
260260
}
261-
pub fn add_lint(&self,
262-
lint: &'static lint::Lint,
263-
id: ast::NodeId,
264-
sp: Span,
265-
msg: String)
261+
pub fn add_lint<S: Into<MultiSpan>>(&self,
262+
lint: &'static lint::Lint,
263+
id: ast::NodeId,
264+
sp: S,
265+
msg: String)
266266
{
267267
self.add_lint_diagnostic(lint, id, (sp, &msg[..]))
268268
}
269+
269270
pub fn add_lint_diagnostic<M>(&self,
270271
lint: &'static lint::Lint,
271272
id: ast::NodeId,

src/librustc_resolve/check_unused.rs

+42-12
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ use Resolver;
2525
use Namespace::{TypeNS, ValueNS};
2626

2727
use rustc::lint;
28+
use rustc::util::nodemap::NodeMap;
2829
use syntax::ast::{self, ViewPathGlob, ViewPathList, ViewPathSimple};
2930
use syntax::visit::{self, Visitor};
30-
use syntax_pos::{Span, DUMMY_SP};
31+
use syntax_pos::{Span, MultiSpan, DUMMY_SP};
3132

3233

3334
struct UnusedImportCheckVisitor<'a, 'b: 'a> {
3435
resolver: &'a mut Resolver<'b>,
36+
/// All the (so far) unused imports, grouped path list
37+
unused_imports: NodeMap<NodeMap<Span>>,
3538
}
3639

3740
// Deref and DerefMut impls allow treating UnusedImportCheckVisitor as Resolver.
@@ -52,23 +55,21 @@ impl<'a, 'b> DerefMut for UnusedImportCheckVisitor<'a, 'b> {
5255
impl<'a, 'b> UnusedImportCheckVisitor<'a, 'b> {
5356
// We have information about whether `use` (import) directives are actually
5457
// used now. If an import is not used at all, we signal a lint error.
55-
fn check_import(&mut self, id: ast::NodeId, span: Span) {
58+
fn check_import(&mut self, item_id: ast::NodeId, id: ast::NodeId, span: Span) {
5659
if !self.used_imports.contains(&(id, TypeNS)) &&
5760
!self.used_imports.contains(&(id, ValueNS)) {
5861
if self.maybe_unused_trait_imports.contains(&id) {
5962
// Check later.
6063
return;
6164
}
62-
let msg = if let Ok(snippet) = self.session.codemap().span_to_snippet(span) {
63-
format!("unused import: `{}`", snippet)
64-
} else {
65-
"unused import".to_string()
66-
};
67-
self.session.add_lint(lint::builtin::UNUSED_IMPORTS, id, span, msg);
65+
self.unused_imports.entry(item_id).or_insert_with(NodeMap).insert(id, span);
6866
} else {
6967
// This trait import is definitely used, in a way other than
7068
// method resolution.
7169
self.maybe_unused_trait_imports.remove(&id);
70+
if let Some(i) = self.unused_imports.get_mut(&item_id) {
71+
i.remove(&id);
72+
}
7273
}
7374
}
7475
}
@@ -98,16 +99,16 @@ impl<'a, 'b> Visitor for UnusedImportCheckVisitor<'a, 'b> {
9899
ast::ItemKind::Use(ref p) => {
99100
match p.node {
100101
ViewPathSimple(..) => {
101-
self.check_import(item.id, p.span)
102+
self.check_import(item.id, item.id, p.span)
102103
}
103104

104105
ViewPathList(_, ref list) => {
105106
for i in list {
106-
self.check_import(i.node.id, i.span);
107+
self.check_import(item.id, i.node.id, i.span);
107108
}
108109
}
109110
ViewPathGlob(_) => {
110-
self.check_import(item.id, p.span)
111+
self.check_import(item.id, item.id, p.span);
111112
}
112113
}
113114
}
@@ -117,6 +118,35 @@ impl<'a, 'b> Visitor for UnusedImportCheckVisitor<'a, 'b> {
117118
}
118119

119120
pub fn check_crate(resolver: &mut Resolver, krate: &ast::Crate) {
120-
let mut visitor = UnusedImportCheckVisitor { resolver: resolver };
121+
let mut visitor = UnusedImportCheckVisitor {
122+
resolver: resolver,
123+
unused_imports: NodeMap(),
124+
};
121125
visit::walk_crate(&mut visitor, krate);
126+
127+
for (id, spans) in &visitor.unused_imports {
128+
let len = spans.len();
129+
let mut spans = spans.values().map(|s| *s).collect::<Vec<Span>>();
130+
spans.sort();
131+
let ms = MultiSpan::from_spans(spans.clone());
132+
let mut span_snippets = spans.iter()
133+
.filter_map(|s| {
134+
match visitor.session.codemap().span_to_snippet(*s) {
135+
Ok(s) => Some(format!("`{}`", s)),
136+
_ => None,
137+
}
138+
}).collect::<Vec<String>>();
139+
span_snippets.sort();
140+
let msg = format!("unused import{}{}",
141+
if len > 1 { "s" } else { "" },
142+
if span_snippets.len() > 0 {
143+
format!(": {}", span_snippets.join(", "))
144+
} else {
145+
String::new()
146+
});
147+
visitor.session.add_lint(lint::builtin::UNUSED_IMPORTS,
148+
*id,
149+
ms,
150+
msg);
151+
}
122152
}

src/libsyntax_pos/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub type FileName = String;
5252
/// able to use many of the functions on spans in codemap and you cannot assume
5353
/// that the length of the span = hi - lo; there may be space in the BytePos
5454
/// range between files.
55-
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
55+
#[derive(Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd)]
5656
pub struct Span {
5757
pub lo: BytePos,
5858
pub hi: BytePos,
@@ -67,7 +67,7 @@ pub struct Span {
6767
/// the error, and would be rendered with `^^^`.
6868
/// - they can have a *label*. In this case, the label is written next
6969
/// to the mark in the snippet when we render.
70-
#[derive(Clone, Debug, PartialEq, Eq)]
70+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
7171
pub struct MultiSpan {
7272
primary_spans: Vec<Span>,
7373
span_labels: Vec<(Span, String)>,
@@ -254,7 +254,7 @@ impl From<Span> for MultiSpan {
254254
}
255255
}
256256

257-
#[derive(PartialEq, Eq, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Copy)]
257+
#[derive(PartialEq, Eq, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Copy, Ord, PartialOrd)]
258258
pub struct ExpnId(pub u32);
259259

260260
pub const NO_EXPANSION: ExpnId = ExpnId(!0);

src/test/compile-fail/lint-unused-imports.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ use std::mem::*; // shouldn't get errors for not using
1717
// everything imported
1818

1919
// Should get errors for both 'Some' and 'None'
20-
use std::option::Option::{Some, None}; //~ ERROR unused import: `Some`
21-
//~^ ERROR unused import: `None`
20+
use std::option::Option::{Some, None};
21+
//~^ ERROR unused imports: `None`, `Some`
22+
//~| ERROR unused imports: `None`, `Some`
2223

2324
use test::A; //~ ERROR unused import: `test::A`
2425
// Be sure that if we just bring some methods into scope that they're also
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use std::cmp::{Eq, Ord, min, PartialEq, PartialOrd};
12+
13+
fn main() {
14+
let _ = min(1, 2);
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
warning: unused imports: `Eq`, `Ord`, `PartialEq`, `PartialOrd`, #[warn(unused_imports)] on by default
2+
--> $DIR/multispan-import-lint.rs:11:16
3+
|
4+
11 | use std::cmp::{Eq, Ord, min, PartialEq, PartialOrd};
5+
| ^^ ^^^ ^^^^^^^^^ ^^^^^^^^^^
6+

0 commit comments

Comments
 (0)