7
7
//
8
8
// Unused trait imports can't be checked until the method resolution. We save
9
9
// 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
10
25
11
26
use std:: ops:: { Deref , DerefMut } ;
12
27
13
28
use crate :: Resolver ;
14
29
use crate :: resolve_imports:: ImportDirectiveSubclass ;
15
30
16
- use rustc:: { lint, ty} ;
17
31
use rustc:: util:: nodemap:: NodeMap ;
32
+ use rustc:: { lint, ty} ;
33
+ use rustc_data_structures:: fx:: FxHashSet ;
18
34
use syntax:: ast;
19
35
use syntax:: visit:: { self , Visitor } ;
20
36
use syntax_pos:: { Span , MultiSpan , DUMMY_SP } ;
21
37
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
+ }
22
50
23
51
struct UnusedImportCheckVisitor < ' a , ' b : ' a > {
24
52
resolver : & ' a mut Resolver < ' b > ,
25
53
/// 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 > ,
27
56
base_id : ast:: NodeId ,
28
57
item_span : Span ,
29
58
}
@@ -46,24 +75,39 @@ impl<'a, 'b> DerefMut for UnusedImportCheckVisitor<'a, 'b> {
46
75
impl < ' a , ' b > UnusedImportCheckVisitor < ' a , ' b > {
47
76
// We have information about whether `use` (import) directives are actually
48
77
// 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 ) {
50
79
let mut used = false ;
51
80
self . per_ns ( |this, ns| used |= this. used_imports . contains ( & ( id, ns) ) ) ;
52
81
if !used {
53
82
if self . maybe_unused_trait_imports . contains ( & id) {
54
83
// Check later.
55
84
return ;
56
85
}
57
- self . unused_imports . entry ( item_id ) . or_default ( ) . insert ( id, span ) ;
86
+ self . unused_import ( self . base_id ) . add ( id) ;
58
87
} else {
59
88
// This trait import is definitely used, in a way other than
60
89
// method resolution.
61
90
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) ;
64
93
}
65
94
}
66
95
}
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
+ }
67
111
}
68
112
69
113
impl < ' a , ' b > Visitor < ' a > for UnusedImportCheckVisitor < ' a , ' b > {
@@ -88,31 +132,112 @@ impl<'a, 'b> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b> {
88
132
// This allows the grouping of all the lints in the same item
89
133
if !nested {
90
134
self . base_id = id;
135
+ self . base_use_tree = Some ( use_tree) ;
91
136
}
92
137
93
138
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
-
101
139
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) ;
106
141
}
107
142
} else {
108
- let base_id = self . base_id ;
109
- self . check_import ( base_id, id, use_tree. span ) ;
143
+ self . check_import ( id) ;
110
144
}
111
145
112
146
visit:: walk_use_tree ( self , use_tree, id) ;
113
147
}
114
148
}
115
149
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
+
116
241
pub fn check_crate ( resolver : & mut Resolver < ' _ > , krate : & ast:: Crate ) {
117
242
for directive in resolver. potentially_unused_imports . iter ( ) {
118
243
match directive. subclass {
@@ -152,14 +277,33 @@ pub fn check_crate(resolver: &mut Resolver<'_>, krate: &ast::Crate) {
152
277
let mut visitor = UnusedImportCheckVisitor {
153
278
resolver,
154
279
unused_imports : Default :: default ( ) ,
280
+ base_use_tree : None ,
155
281
base_id : ast:: DUMMY_NODE_ID ,
156
282
item_span : DUMMY_SP ,
157
283
} ;
158
284
visit:: walk_crate ( & mut visitor, krate) ;
159
285
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
+
161
306
let len = spans. len ( ) ;
162
- let mut spans = spans. values ( ) . cloned ( ) . collect :: < Vec < Span > > ( ) ;
163
307
spans. sort ( ) ;
164
308
let ms = MultiSpan :: from_spans ( spans. clone ( ) ) ;
165
309
let mut span_snippets = spans. iter ( )
@@ -177,6 +321,21 @@ pub fn check_crate(resolver: &mut Resolver<'_>, krate: &ast::Crate) {
177
321
} else {
178
322
String :: new( )
179
323
} ) ;
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
+ ) ;
181
340
}
182
341
}
0 commit comments