]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // |
2 | // Unused import checking | |
3 | // | |
4 | // Although this is mostly a lint pass, it lives in here because it depends on | |
5 | // resolve data structures and because it finalises the privacy information for | |
74b04a01 | 6 | // `use` items. |
1a4d82fc | 7 | // |
a7813a04 | 8 | // Unused trait imports can't be checked until the method resolution. We save |
0531ce1d | 9 | // candidates here, and do the actual check in librustc_typeck/check_unused.rs. |
9fa01778 XL |
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 | |
1a4d82fc | 25 | |
74b04a01 | 26 | use crate::imports::ImportKind; |
9fa01778 | 27 | use crate::Resolver; |
1a4d82fc | 28 | |
3dfed10e | 29 | use rustc_ast as ast; |
74b04a01 XL |
30 | use rustc_ast::node_id::NodeMap; |
31 | use rustc_ast::visit::{self, Visitor}; | |
f035d41b | 32 | use rustc_ast_lowering::ResolverAstLowering; |
9fa01778 | 33 | use rustc_data_structures::fx::FxHashSet; |
dfeec247 | 34 | use rustc_errors::pluralize; |
ba9703b0 XL |
35 | use rustc_middle::ty; |
36 | use rustc_session::lint::builtin::{MACRO_USE_EXTERN_CRATE, UNUSED_IMPORTS}; | |
dfeec247 | 37 | use rustc_session::lint::BuiltinLintDiagnostics; |
dfeec247 | 38 | use rustc_span::{MultiSpan, Span, DUMMY_SP}; |
e9174d1e | 39 | |
9fa01778 XL |
40 | struct UnusedImport<'a> { |
41 | use_tree: &'a ast::UseTree, | |
42 | use_tree_id: ast::NodeId, | |
43 | item_span: Span, | |
44 | unused: FxHashSet<ast::NodeId>, | |
45 | } | |
46 | ||
47 | impl<'a> UnusedImport<'a> { | |
48 | fn add(&mut self, id: ast::NodeId) { | |
49 | self.unused.insert(id); | |
50 | } | |
51 | } | |
1a4d82fc | 52 | |
dc9dc135 | 53 | struct UnusedImportCheckVisitor<'a, 'b> { |
416331ca | 54 | r: &'a mut Resolver<'b>, |
476ff2be | 55 | /// All the (so far) unused imports, grouped path list |
9fa01778 XL |
56 | unused_imports: NodeMap<UnusedImport<'a>>, |
57 | base_use_tree: Option<&'a ast::UseTree>, | |
ff7c6d11 XL |
58 | base_id: ast::NodeId, |
59 | item_span: Span, | |
1a4d82fc JJ |
60 | } |
61 | ||
0731742a | 62 | impl<'a, 'b> UnusedImportCheckVisitor<'a, 'b> { |
74b04a01 | 63 | // We have information about whether `use` (import) items are actually |
54a0048b | 64 | // used now. If an import is not used at all, we signal a lint error. |
9fa01778 | 65 | fn check_import(&mut self, id: ast::NodeId) { |
476ff2be | 66 | let mut used = false; |
416331ca | 67 | self.r.per_ns(|this, ns| used |= this.used_imports.contains(&(id, ns))); |
f035d41b | 68 | let def_id = self.r.local_def_id(id); |
476ff2be | 69 | if !used { |
f9f354fc | 70 | if self.r.maybe_unused_trait_imports.contains(&def_id) { |
a7813a04 XL |
71 | // Check later. |
72 | return; | |
73 | } | |
9fa01778 | 74 | self.unused_import(self.base_id).add(id); |
a7813a04 XL |
75 | } else { |
76 | // This trait import is definitely used, in a way other than | |
77 | // method resolution. | |
f9f354fc | 78 | self.r.maybe_unused_trait_imports.remove(&def_id); |
9fa01778 XL |
79 | if let Some(i) = self.unused_imports.get_mut(&self.base_id) { |
80 | i.unused.remove(&id); | |
476ff2be | 81 | } |
1a4d82fc | 82 | } |
1a4d82fc | 83 | } |
9fa01778 XL |
84 | |
85 | fn unused_import(&mut self, id: ast::NodeId) -> &mut UnusedImport<'a> { | |
86 | let use_tree_id = self.base_id; | |
87 | let use_tree = self.base_use_tree.unwrap(); | |
88 | let item_span = self.item_span; | |
89 | ||
dfeec247 XL |
90 | self.unused_imports.entry(id).or_insert_with(|| UnusedImport { |
91 | use_tree, | |
92 | use_tree_id, | |
93 | item_span, | |
94 | unused: FxHashSet::default(), | |
95 | }) | |
9fa01778 | 96 | } |
1a4d82fc JJ |
97 | } |
98 | ||
0731742a | 99 | impl<'a, 'b> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b> { |
476ff2be | 100 | fn visit_item(&mut self, item: &'a ast::Item) { |
ff7c6d11 XL |
101 | self.item_span = item.span; |
102 | ||
1a4d82fc JJ |
103 | // Ignore is_public import statements because there's no way to be sure |
104 | // whether they're used or not. Also ignore imports with a dummy span | |
105 | // because this means that they were generated in some fashion by the | |
106 | // compiler and we don't need to consider them. | |
e74abb32 | 107 | if let ast::ItemKind::Use(..) = item.kind { |
8faf50e0 | 108 | if item.vis.node.is_pub() || item.span.is_dummy() { |
ff7c6d11 XL |
109 | return; |
110 | } | |
111 | } | |
112 | ||
113 | visit::walk_item(self, item); | |
114 | } | |
115 | ||
116 | fn visit_use_tree(&mut self, use_tree: &'a ast::UseTree, id: ast::NodeId, nested: bool) { | |
117 | // Use the base UseTree's NodeId as the item id | |
118 | // This allows the grouping of all the lints in the same item | |
119 | if !nested { | |
120 | self.base_id = id; | |
9fa01778 | 121 | self.base_use_tree = Some(use_tree); |
1a4d82fc JJ |
122 | } |
123 | ||
ff7c6d11 | 124 | if let ast::UseTreeKind::Nested(ref items) = use_tree.kind { |
0bf4aa26 | 125 | if items.is_empty() { |
9fa01778 | 126 | self.unused_import(self.base_id).add(id); |
1a4d82fc | 127 | } |
ff7c6d11 | 128 | } else { |
9fa01778 | 129 | self.check_import(id); |
1a4d82fc | 130 | } |
ff7c6d11 XL |
131 | |
132 | visit::walk_use_tree(self, use_tree, id); | |
1a4d82fc JJ |
133 | } |
134 | } | |
135 | ||
9fa01778 XL |
136 | enum UnusedSpanResult { |
137 | Used, | |
138 | FlatUnused(Span, Span), | |
139 | NestedFullUnused(Vec<Span>, Span), | |
140 | NestedPartialUnused(Vec<Span>, Vec<Span>), | |
141 | } | |
142 | ||
143 | fn calc_unused_spans( | |
144 | unused_import: &UnusedImport<'_>, | |
145 | use_tree: &ast::UseTree, | |
146 | use_tree_id: ast::NodeId, | |
147 | ) -> UnusedSpanResult { | |
148 | // The full span is the whole item's span if this current tree is not nested inside another | |
149 | // This tells rustfix to remove the whole item if all the imports are unused | |
150 | let full_span = if unused_import.use_tree.span == use_tree.span { | |
151 | unused_import.item_span | |
152 | } else { | |
153 | use_tree.span | |
154 | }; | |
155 | match use_tree.kind { | |
156 | ast::UseTreeKind::Simple(..) | ast::UseTreeKind::Glob => { | |
157 | if unused_import.unused.contains(&use_tree_id) { | |
158 | UnusedSpanResult::FlatUnused(use_tree.span, full_span) | |
159 | } else { | |
160 | UnusedSpanResult::Used | |
161 | } | |
162 | } | |
163 | ast::UseTreeKind::Nested(ref nested) => { | |
74b04a01 | 164 | if nested.is_empty() { |
9fa01778 XL |
165 | return UnusedSpanResult::FlatUnused(use_tree.span, full_span); |
166 | } | |
167 | ||
168 | let mut unused_spans = Vec::new(); | |
169 | let mut to_remove = Vec::new(); | |
170 | let mut all_nested_unused = true; | |
171 | let mut previous_unused = false; | |
172 | for (pos, (use_tree, use_tree_id)) in nested.iter().enumerate() { | |
173 | let remove = match calc_unused_spans(unused_import, use_tree, *use_tree_id) { | |
174 | UnusedSpanResult::Used => { | |
175 | all_nested_unused = false; | |
176 | None | |
177 | } | |
178 | UnusedSpanResult::FlatUnused(span, remove) => { | |
179 | unused_spans.push(span); | |
180 | Some(remove) | |
181 | } | |
182 | UnusedSpanResult::NestedFullUnused(mut spans, remove) => { | |
183 | unused_spans.append(&mut spans); | |
184 | Some(remove) | |
185 | } | |
186 | UnusedSpanResult::NestedPartialUnused(mut spans, mut to_remove_extra) => { | |
187 | all_nested_unused = false; | |
188 | unused_spans.append(&mut spans); | |
189 | to_remove.append(&mut to_remove_extra); | |
190 | None | |
191 | } | |
192 | }; | |
193 | if let Some(remove) = remove { | |
194 | let remove_span = if nested.len() == 1 { | |
195 | remove | |
196 | } else if pos == nested.len() - 1 || !all_nested_unused { | |
197 | // Delete everything from the end of the last import, to delete the | |
198 | // previous comma | |
199 | nested[pos - 1].0.span.shrink_to_hi().to(use_tree.span) | |
200 | } else { | |
201 | // Delete everything until the next import, to delete the trailing commas | |
202 | use_tree.span.to(nested[pos + 1].0.span.shrink_to_lo()) | |
203 | }; | |
204 | ||
205 | // Try to collapse adjacent spans into a single one. This prevents all cases of | |
206 | // overlapping removals, which are not supported by rustfix | |
207 | if previous_unused && !to_remove.is_empty() { | |
208 | let previous = to_remove.pop().unwrap(); | |
209 | to_remove.push(previous.to(remove_span)); | |
210 | } else { | |
211 | to_remove.push(remove_span); | |
212 | } | |
213 | } | |
214 | previous_unused = remove.is_some(); | |
215 | } | |
216 | if unused_spans.is_empty() { | |
217 | UnusedSpanResult::Used | |
218 | } else if all_nested_unused { | |
219 | UnusedSpanResult::NestedFullUnused(unused_spans, full_span) | |
220 | } else { | |
221 | UnusedSpanResult::NestedPartialUnused(unused_spans, to_remove) | |
222 | } | |
223 | } | |
224 | } | |
225 | } | |
226 | ||
416331ca XL |
227 | impl Resolver<'_> { |
228 | crate fn check_unused(&mut self, krate: &ast::Crate) { | |
74b04a01 XL |
229 | for import in self.potentially_unused_imports.iter() { |
230 | match import.kind { | |
231 | _ if import.used.get() | |
232 | || import.vis.get() == ty::Visibility::Public | |
233 | || import.span.is_dummy() => | |
dfeec247 | 234 | { |
74b04a01 XL |
235 | if let ImportKind::MacroUse = import.kind { |
236 | if !import.span.is_dummy() { | |
e74abb32 | 237 | self.lint_buffer.buffer_lint( |
ba9703b0 | 238 | MACRO_USE_EXTERN_CRATE, |
74b04a01 XL |
239 | import.id, |
240 | import.span, | |
241 | "deprecated `#[macro_use]` attribute used to \ | |
416331ca | 242 | import macros should be replaced at use sites \ |
74b04a01 | 243 | with a `use` item to import the macro \ |
416331ca XL |
244 | instead", |
245 | ); | |
246 | } | |
8faf50e0 XL |
247 | } |
248 | } | |
74b04a01 | 249 | ImportKind::ExternCrate { .. } => { |
f035d41b | 250 | let def_id = self.local_def_id(import.id); |
f9f354fc | 251 | self.maybe_unused_extern_crates.push((def_id, import.span)); |
416331ca | 252 | } |
74b04a01 | 253 | ImportKind::MacroUse => { |
416331ca | 254 | let msg = "unused `#[macro_use]` import"; |
ba9703b0 | 255 | self.lint_buffer.buffer_lint(UNUSED_IMPORTS, import.id, import.span, msg); |
416331ca XL |
256 | } |
257 | _ => {} | |
8faf50e0 | 258 | } |
32a655c1 | 259 | } |
94b46f34 | 260 | |
416331ca XL |
261 | let mut visitor = UnusedImportCheckVisitor { |
262 | r: self, | |
263 | unused_imports: Default::default(), | |
264 | base_use_tree: None, | |
265 | base_id: ast::DUMMY_NODE_ID, | |
266 | item_span: DUMMY_SP, | |
9fa01778 | 267 | }; |
416331ca | 268 | visit::walk_crate(&mut visitor, krate); |
9fa01778 | 269 | |
416331ca XL |
270 | for unused in visitor.unused_imports.values() { |
271 | let mut fixes = Vec::new(); | |
272 | let mut spans = match calc_unused_spans(unused, unused.use_tree, unused.use_tree_id) { | |
273 | UnusedSpanResult::Used => continue, | |
274 | UnusedSpanResult::FlatUnused(span, remove) => { | |
275 | fixes.push((remove, String::new())); | |
276 | vec![span] | |
476ff2be | 277 | } |
416331ca XL |
278 | UnusedSpanResult::NestedFullUnused(spans, remove) => { |
279 | fixes.push((remove, String::new())); | |
280 | spans | |
281 | } | |
282 | UnusedSpanResult::NestedPartialUnused(spans, remove) => { | |
283 | for fix in &remove { | |
284 | fixes.push((*fix, String::new())); | |
285 | } | |
286 | spans | |
287 | } | |
288 | }; | |
9fa01778 | 289 | |
416331ca XL |
290 | let len = spans.len(); |
291 | spans.sort(); | |
292 | let ms = MultiSpan::from_spans(spans.clone()); | |
dfeec247 XL |
293 | let mut span_snippets = spans |
294 | .iter() | |
295 | .filter_map(|s| match visitor.r.session.source_map().span_to_snippet(*s) { | |
296 | Ok(s) => Some(format!("`{}`", s)), | |
297 | _ => None, | |
298 | }) | |
299 | .collect::<Vec<String>>(); | |
416331ca | 300 | span_snippets.sort(); |
dfeec247 XL |
301 | let msg = format!( |
302 | "unused import{}{}", | |
303 | pluralize!(len), | |
304 | if !span_snippets.is_empty() { | |
305 | format!(": {}", span_snippets.join(", ")) | |
306 | } else { | |
307 | String::new() | |
308 | } | |
309 | ); | |
9fa01778 | 310 | |
416331ca XL |
311 | let fix_msg = if fixes.len() == 1 && fixes[0].0 == unused.item_span { |
312 | "remove the whole `use` item" | |
313 | } else if spans.len() > 1 { | |
314 | "remove the unused imports" | |
315 | } else { | |
316 | "remove the unused import" | |
317 | }; | |
318 | ||
e74abb32 | 319 | visitor.r.lint_buffer.buffer_lint_with_diagnostic( |
ba9703b0 | 320 | UNUSED_IMPORTS, |
416331ca XL |
321 | unused.use_tree_id, |
322 | ms, | |
323 | &msg, | |
dfeec247 | 324 | BuiltinLintDiagnostics::UnusedImports(fix_msg.into(), fixes), |
416331ca XL |
325 | ); |
326 | } | |
476ff2be | 327 | } |
1a4d82fc | 328 | } |