77//
88// Unused trait imports can't be checked until the method resolution. We save
99// candidates here, and do the actual check in librustc_typeck/check_unused.rs.
10+ //
11+ // Checking for unused imports is split into three steps:
12+ //
13+ // - `UnusedImportCheckVisitor` walks the AST to find all the unused imports
14+ // inside of `UseTree`s, recording their `NodeId`s and grouping them by
15+ // the parent `use` item
16+ //
17+ // - `calc_unused_spans` then walks over all the `use` items marked in the
18+ // previous step to collect the spans associated with the `NodeId`s and to
19+ // calculate the spans that can be removed by rustfix; This is done in a
20+ // separate step to be able to collapse the adjacent spans that rustfix
21+ // will remove
22+ //
23+ // - `check_crate` finally emits the diagnostics based on the data generated
24+ // in the last step
1025
1126use std:: ops:: { Deref , DerefMut } ;
1227
1328use crate :: Resolver ;
1429use crate :: resolve_imports:: ImportDirectiveSubclass ;
1530
16- use rustc:: { lint, ty} ;
1731use rustc:: util:: nodemap:: NodeMap ;
32+ use rustc:: { lint, ty} ;
33+ use rustc_data_structures:: fx:: FxHashSet ;
1834use syntax:: ast;
1935use syntax:: visit:: { self , Visitor } ;
2036use syntax_pos:: { Span , MultiSpan , DUMMY_SP } ;
2137
38+ struct UnusedImport < ' a > {
39+ use_tree : & ' a ast:: UseTree ,
40+ use_tree_id : ast:: NodeId ,
41+ item_span : Span ,
42+ unused : FxHashSet < ast:: NodeId > ,
43+ }
44+
45+ impl < ' a > UnusedImport < ' a > {
46+ fn add ( & mut self , id : ast:: NodeId ) {
47+ self . unused . insert ( id) ;
48+ }
49+ }
2250
2351struct UnusedImportCheckVisitor < ' a , ' b : ' a > {
2452 resolver : & ' a mut Resolver < ' b > ,
2553 /// All the (so far) unused imports, grouped path list
26- unused_imports : NodeMap < NodeMap < Span > > ,
54+ unused_imports : NodeMap < UnusedImport < ' a > > ,
55+ base_use_tree : Option < & ' a ast:: UseTree > ,
2756 base_id : ast:: NodeId ,
2857 item_span : Span ,
2958}
@@ -46,24 +75,39 @@ impl<'a, 'b> DerefMut for UnusedImportCheckVisitor<'a, 'b> {
4675impl < ' a , ' b > UnusedImportCheckVisitor < ' a , ' b > {
4776 // We have information about whether `use` (import) directives are actually
4877 // used now. If an import is not used at all, we signal a lint error.
49- fn check_import ( & mut self , item_id : ast :: NodeId , id : ast:: NodeId , span : Span ) {
78+ fn check_import ( & mut self , id : ast:: NodeId ) {
5079 let mut used = false ;
5180 self . per_ns ( |this, ns| used |= this. used_imports . contains ( & ( id, ns) ) ) ;
5281 if !used {
5382 if self . maybe_unused_trait_imports . contains ( & id) {
5483 // Check later.
5584 return ;
5685 }
57- self . unused_imports . entry ( item_id ) . or_default ( ) . insert ( id, span ) ;
86+ self . unused_import ( self . base_id ) . add ( id) ;
5887 } else {
5988 // This trait import is definitely used, in a way other than
6089 // method resolution.
6190 self . maybe_unused_trait_imports . remove ( & id) ;
62- if let Some ( i) = self . unused_imports . get_mut ( & item_id ) {
63- i. remove ( & id) ;
91+ if let Some ( i) = self . unused_imports . get_mut ( & self . base_id ) {
92+ i. unused . remove ( & id) ;
6493 }
6594 }
6695 }
96+
97+ fn unused_import ( & mut self , id : ast:: NodeId ) -> & mut UnusedImport < ' a > {
98+ let use_tree_id = self . base_id ;
99+ let use_tree = self . base_use_tree . unwrap ( ) ;
100+ let item_span = self . item_span ;
101+
102+ self . unused_imports
103+ . entry ( id)
104+ . or_insert_with ( || UnusedImport {
105+ use_tree,
106+ use_tree_id,
107+ item_span,
108+ unused : FxHashSet :: default ( ) ,
109+ } )
110+ }
67111}
68112
69113impl < ' a , ' b > Visitor < ' a > for UnusedImportCheckVisitor < ' a , ' b > {
@@ -88,31 +132,112 @@ impl<'a, 'b> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b> {
88132 // This allows the grouping of all the lints in the same item
89133 if !nested {
90134 self . base_id = id;
135+ self . base_use_tree = Some ( use_tree) ;
91136 }
92137
93138 if let ast:: UseTreeKind :: Nested ( ref items) = use_tree. kind {
94- // If it's the parent group, cover the entire use item
95- let span = if nested {
96- use_tree. span
97- } else {
98- self . item_span
99- } ;
100-
101139 if items. is_empty ( ) {
102- self . unused_imports
103- . entry ( self . base_id )
104- . or_default ( )
105- . insert ( id, span) ;
140+ self . unused_import ( self . base_id ) . add ( id) ;
106141 }
107142 } else {
108- let base_id = self . base_id ;
109- self . check_import ( base_id, id, use_tree. span ) ;
143+ self . check_import ( id) ;
110144 }
111145
112146 visit:: walk_use_tree ( self , use_tree, id) ;
113147 }
114148}
115149
150+ enum UnusedSpanResult {
151+ Used ,
152+ FlatUnused ( Span , Span ) ,
153+ NestedFullUnused ( Vec < Span > , Span ) ,
154+ NestedPartialUnused ( Vec < Span > , Vec < Span > ) ,
155+ }
156+
157+ fn calc_unused_spans (
158+ unused_import : & UnusedImport < ' _ > ,
159+ use_tree : & ast:: UseTree ,
160+ use_tree_id : ast:: NodeId ,
161+ ) -> UnusedSpanResult {
162+ // The full span is the whole item's span if this current tree is not nested inside another
163+ // This tells rustfix to remove the whole item if all the imports are unused
164+ let full_span = if unused_import. use_tree . span == use_tree. span {
165+ unused_import. item_span
166+ } else {
167+ use_tree. span
168+ } ;
169+ match use_tree. kind {
170+ ast:: UseTreeKind :: Simple ( ..) | ast:: UseTreeKind :: Glob => {
171+ if unused_import. unused . contains ( & use_tree_id) {
172+ UnusedSpanResult :: FlatUnused ( use_tree. span , full_span)
173+ } else {
174+ UnusedSpanResult :: Used
175+ }
176+ }
177+ ast:: UseTreeKind :: Nested ( ref nested) => {
178+ if nested. len ( ) == 0 {
179+ return UnusedSpanResult :: FlatUnused ( use_tree. span , full_span) ;
180+ }
181+
182+ let mut unused_spans = Vec :: new ( ) ;
183+ let mut to_remove = Vec :: new ( ) ;
184+ let mut all_nested_unused = true ;
185+ let mut previous_unused = false ;
186+ for ( pos, ( use_tree, use_tree_id) ) in nested. iter ( ) . enumerate ( ) {
187+ let remove = match calc_unused_spans ( unused_import, use_tree, * use_tree_id) {
188+ UnusedSpanResult :: Used => {
189+ all_nested_unused = false ;
190+ None
191+ }
192+ UnusedSpanResult :: FlatUnused ( span, remove) => {
193+ unused_spans. push ( span) ;
194+ Some ( remove)
195+ }
196+ UnusedSpanResult :: NestedFullUnused ( mut spans, remove) => {
197+ unused_spans. append ( & mut spans) ;
198+ Some ( remove)
199+ }
200+ UnusedSpanResult :: NestedPartialUnused ( mut spans, mut to_remove_extra) => {
201+ all_nested_unused = false ;
202+ unused_spans. append ( & mut spans) ;
203+ to_remove. append ( & mut to_remove_extra) ;
204+ None
205+ }
206+ } ;
207+ if let Some ( remove) = remove {
208+ let remove_span = if nested. len ( ) == 1 {
209+ remove
210+ } else if pos == nested. len ( ) - 1 || !all_nested_unused {
211+ // Delete everything from the end of the last import, to delete the
212+ // previous comma
213+ nested[ pos - 1 ] . 0 . span . shrink_to_hi ( ) . to ( use_tree. span )
214+ } else {
215+ // Delete everything until the next import, to delete the trailing commas
216+ use_tree. span . to ( nested[ pos + 1 ] . 0 . span . shrink_to_lo ( ) )
217+ } ;
218+
219+ // Try to collapse adjacent spans into a single one. This prevents all cases of
220+ // overlapping removals, which are not supported by rustfix
221+ if previous_unused && !to_remove. is_empty ( ) {
222+ let previous = to_remove. pop ( ) . unwrap ( ) ;
223+ to_remove. push ( previous. to ( remove_span) ) ;
224+ } else {
225+ to_remove. push ( remove_span) ;
226+ }
227+ }
228+ previous_unused = remove. is_some ( ) ;
229+ }
230+ if unused_spans. is_empty ( ) {
231+ UnusedSpanResult :: Used
232+ } else if all_nested_unused {
233+ UnusedSpanResult :: NestedFullUnused ( unused_spans, full_span)
234+ } else {
235+ UnusedSpanResult :: NestedPartialUnused ( unused_spans, to_remove)
236+ }
237+ }
238+ }
239+ }
240+
116241pub fn check_crate ( resolver : & mut Resolver < ' _ > , krate : & ast:: Crate ) {
117242 for directive in resolver. potentially_unused_imports . iter ( ) {
118243 match directive. subclass {
@@ -152,14 +277,33 @@ pub fn check_crate(resolver: &mut Resolver<'_>, krate: &ast::Crate) {
152277 let mut visitor = UnusedImportCheckVisitor {
153278 resolver,
154279 unused_imports : Default :: default ( ) ,
280+ base_use_tree : None ,
155281 base_id : ast:: DUMMY_NODE_ID ,
156282 item_span : DUMMY_SP ,
157283 } ;
158284 visit:: walk_crate ( & mut visitor, krate) ;
159285
160- for ( id, spans) in & visitor. unused_imports {
286+ for unused in visitor. unused_imports . values ( ) {
287+ let mut fixes = Vec :: new ( ) ;
288+ let mut spans = match calc_unused_spans ( unused, unused. use_tree , unused. use_tree_id ) {
289+ UnusedSpanResult :: Used => continue ,
290+ UnusedSpanResult :: FlatUnused ( span, remove) => {
291+ fixes. push ( ( remove, String :: new ( ) ) ) ;
292+ vec ! [ span]
293+ }
294+ UnusedSpanResult :: NestedFullUnused ( spans, remove) => {
295+ fixes. push ( ( remove, String :: new ( ) ) ) ;
296+ spans
297+ }
298+ UnusedSpanResult :: NestedPartialUnused ( spans, remove) => {
299+ for fix in & remove {
300+ fixes. push ( ( * fix, String :: new ( ) ) ) ;
301+ }
302+ spans
303+ }
304+ } ;
305+
161306 let len = spans. len ( ) ;
162- let mut spans = spans. values ( ) . cloned ( ) . collect :: < Vec < Span > > ( ) ;
163307 spans. sort ( ) ;
164308 let ms = MultiSpan :: from_spans ( spans. clone ( ) ) ;
165309 let mut span_snippets = spans. iter ( )
@@ -177,6 +321,21 @@ pub fn check_crate(resolver: &mut Resolver<'_>, krate: &ast::Crate) {
177321 } else {
178322 String :: new( )
179323 } ) ;
180- visitor. session . buffer_lint ( lint:: builtin:: UNUSED_IMPORTS , * id, ms, & msg) ;
324+
325+ let fix_msg = if fixes. len ( ) == 1 && fixes[ 0 ] . 0 == unused. item_span {
326+ "remove the whole `use` item"
327+ } else if spans. len ( ) > 1 {
328+ "remove the unused imports"
329+ } else {
330+ "remove the unused import"
331+ } ;
332+
333+ visitor. session . buffer_lint_with_diagnostic (
334+ lint:: builtin:: UNUSED_IMPORTS ,
335+ unused. use_tree_id ,
336+ ms,
337+ & msg,
338+ lint:: builtin:: BuiltinLintDiagnostics :: UnusedImports ( fix_msg. into ( ) , fixes) ,
339+ ) ;
181340 }
182341}
0 commit comments