]>
Commit | Line | Data |
---|---|---|
74b04a01 | 1 | use rustc_ast::ast; |
94b46f34 | 2 | use rustc_data_structures::fx::FxHashMap; |
dfeec247 XL |
3 | use rustc_errors::Applicability; |
4 | use rustc_hir as hir; | |
5 | use rustc_hir::def_id::{DefId, DefIdSet, LOCAL_CRATE}; | |
6 | use rustc_hir::itemlikevisit::ItemLikeVisitor; | |
ba9703b0 XL |
7 | use rustc_middle::ty::TyCtxt; |
8 | use rustc_session::lint; | |
dfeec247 | 9 | use rustc_span::Span; |
94b46f34 | 10 | |
416331ca | 11 | pub fn check_crate(tcx: TyCtxt<'_>) { |
a1dfa0c6 | 12 | let mut used_trait_imports = DefIdSet::default(); |
0731742a XL |
13 | for &body_id in tcx.hir().krate().bodies.keys() { |
14 | let item_def_id = tcx.hir().body_owner_def_id(body_id); | |
ba9703b0 | 15 | let imports = tcx.used_trait_imports(item_def_id.to_def_id()); |
94b46f34 XL |
16 | debug!("GatherVisitor: item_def_id={:?} with imports {:#?}", item_def_id, imports); |
17 | used_trait_imports.extend(imports.iter()); | |
18 | } | |
19 | ||
20 | let mut visitor = CheckVisitor { tcx, used_trait_imports }; | |
0731742a | 21 | tcx.hir().krate().visit_all_item_likes(&mut visitor); |
94b46f34 XL |
22 | |
23 | unused_crates_lint(tcx); | |
24 | } | |
25 | ||
dc9dc135 | 26 | impl ItemLikeVisitor<'v> for CheckVisitor<'tcx> { |
dfeec247 | 27 | fn visit_item(&mut self, item: &hir::Item<'_>) { |
8faf50e0 | 28 | if item.vis.node.is_pub() || item.span.is_dummy() { |
94b46f34 XL |
29 | return; |
30 | } | |
e74abb32 | 31 | if let hir::ItemKind::Use(ref path, _) = item.kind { |
532ac7d7 | 32 | self.check_import(item.hir_id, path.span); |
94b46f34 XL |
33 | } |
34 | } | |
35 | ||
dfeec247 | 36 | fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem<'_>) {} |
94b46f34 | 37 | |
dfeec247 | 38 | fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem<'_>) {} |
94b46f34 XL |
39 | } |
40 | ||
dc9dc135 XL |
41 | struct CheckVisitor<'tcx> { |
42 | tcx: TyCtxt<'tcx>, | |
8bb4bdeb | 43 | used_trait_imports: DefIdSet, |
a7813a04 XL |
44 | } |
45 | ||
dc9dc135 | 46 | impl CheckVisitor<'tcx> { |
532ac7d7 | 47 | fn check_import(&self, id: hir::HirId, span: Span) { |
416331ca | 48 | let def_id = self.tcx.hir().local_def_id(id); |
ea8adc8c | 49 | if !self.tcx.maybe_unused_trait_import(def_id) { |
a7813a04 XL |
50 | return; |
51 | } | |
8bb4bdeb | 52 | |
532ac7d7 | 53 | if self.used_trait_imports.contains(&def_id) { |
a7813a04 XL |
54 | return; |
55 | } | |
c30ab7b3 | 56 | |
74b04a01 XL |
57 | self.tcx.struct_span_lint_hir(lint::builtin::UNUSED_IMPORTS, id, span, |lint| { |
58 | let msg = if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { | |
59 | format!("unused import: `{}`", snippet) | |
60 | } else { | |
61 | "unused import".to_owned() | |
62 | }; | |
63 | lint.build(&msg).emit(); | |
64 | }); | |
a7813a04 XL |
65 | } |
66 | } | |
67 | ||
416331ca | 68 | fn unused_crates_lint(tcx: TyCtxt<'_>) { |
94b46f34 XL |
69 | let lint = lint::builtin::UNUSED_EXTERN_CRATES; |
70 | ||
71 | // Collect first the crates that are completely unused. These we | |
72 | // can always suggest removing (no matter which edition we are | |
73 | // in). | |
dfeec247 XL |
74 | let unused_extern_crates: FxHashMap<DefId, Span> = tcx |
75 | .maybe_unused_extern_crates(LOCAL_CRATE) | |
94b46f34 XL |
76 | .iter() |
77 | .filter(|&&(def_id, _)| { | |
78 | // The `def_id` here actually was calculated during resolution (at least | |
79 | // at the time of this writing) and is being shipped to us via a side | |
80 | // channel of the tcx. There may have been extra expansion phases, | |
81 | // however, which ended up removing the `def_id` *after* expansion such | |
82 | // as the `ReplaceBodyWithLoop` pass (which is a bit of a hack, but hey) | |
83 | // | |
84 | // As a result we need to verify that `def_id` is indeed still valid for | |
85 | // our AST and actually present in the HIR map. If it's not there then | |
86 | // there's safely nothing to warn about, and otherwise we carry on with | |
87 | // our execution. | |
88 | // | |
89 | // Note that if we carry through to the `extern_mod_stmt_cnum` query | |
90 | // below it'll cause a panic because `def_id` is actually bogus at this | |
91 | // point in time otherwise. | |
532ac7d7 | 92 | if let Some(id) = tcx.hir().as_local_hir_id(def_id) { |
dc9dc135 | 93 | if tcx.hir().find(id).is_none() { |
94b46f34 XL |
94 | return false; |
95 | } | |
96 | } | |
97 | true | |
98 | }) | |
99 | .filter(|&&(def_id, _)| { | |
a1dfa0c6 | 100 | tcx.extern_mod_stmt_cnum(def_id).map_or(true, |cnum| { |
dfeec247 XL |
101 | !tcx.is_compiler_builtins(cnum) |
102 | && !tcx.is_panic_runtime(cnum) | |
103 | && !tcx.has_global_allocator(cnum) | |
104 | && !tcx.has_panic_handler(cnum) | |
a1dfa0c6 | 105 | }) |
94b46f34 XL |
106 | }) |
107 | .cloned() | |
108 | .collect(); | |
109 | ||
110 | // Collect all the extern crates (in a reliable order). | |
111 | let mut crates_to_lint = vec![]; | |
0731742a | 112 | tcx.hir().krate().visit_all_item_likes(&mut CollectExternCrateVisitor { |
94b46f34 XL |
113 | tcx, |
114 | crates_to_lint: &mut crates_to_lint, | |
115 | }); | |
116 | ||
117 | for extern_crate in &crates_to_lint { | |
532ac7d7 | 118 | let id = tcx.hir().as_local_hir_id(extern_crate.def_id).unwrap(); |
dc9dc135 | 119 | let item = tcx.hir().expect_item(id); |
94b46f34 XL |
120 | |
121 | // If the crate is fully unused, we suggest removing it altogether. | |
122 | // We do this in any edition. | |
b7449926 XL |
123 | if extern_crate.warn_if_unused { |
124 | if let Some(&span) = unused_extern_crates.get(&extern_crate.def_id) { | |
74b04a01 XL |
125 | tcx.struct_span_lint_hir(lint, id, span, |lint| { |
126 | // Removal suggestion span needs to include attributes (Issue #54400) | |
127 | let span_with_attrs = tcx | |
128 | .get_attrs(extern_crate.def_id) | |
129 | .iter() | |
130 | .map(|attr| attr.span) | |
131 | .fold(span, |acc, attr_span| acc.to(attr_span)); | |
132 | ||
133 | lint.build("unused extern crate") | |
134 | .span_suggestion_short( | |
135 | span_with_attrs, | |
136 | "remove it", | |
137 | String::new(), | |
138 | Applicability::MachineApplicable, | |
139 | ) | |
140 | .emit(); | |
141 | }); | |
b7449926 XL |
142 | continue; |
143 | } | |
a7813a04 | 144 | } |
94b46f34 XL |
145 | |
146 | // If we are not in Rust 2018 edition, then we don't make any further | |
147 | // suggestions. | |
148 | if !tcx.sess.rust_2018() { | |
149 | continue; | |
a7813a04 | 150 | } |
476ff2be | 151 | |
4462d4a0 XL |
152 | // If the extern crate isn't in the extern prelude, |
153 | // there is no way it can be written as an `use`. | |
0731742a | 154 | let orig_name = extern_crate.orig_name.unwrap_or(item.ident.name); |
0bf4aa26 | 155 | if !tcx.extern_prelude.get(&orig_name).map_or(false, |from_item| !from_item) { |
4462d4a0 XL |
156 | continue; |
157 | } | |
158 | ||
48663c56 XL |
159 | // If the extern crate is renamed, then we cannot suggest replacing it with a use as this |
160 | // would not insert the new name into the prelude, where other imports in the crate may be | |
161 | // expecting it. | |
162 | if extern_crate.orig_name.is_some() { | |
163 | continue; | |
164 | } | |
165 | ||
94b46f34 XL |
166 | // If the extern crate has any attributes, they may have funky |
167 | // semantics we can't faithfully represent using `use` (most | |
168 | // notably `#[macro_use]`). Ignore it. | |
169 | if !tcx.get_attrs(extern_crate.def_id).is_empty() { | |
170 | continue; | |
171 | } | |
74b04a01 XL |
172 | tcx.struct_span_lint_hir(lint, id, extern_crate.span, |lint| { |
173 | // Otherwise, we can convert it into a `use` of some kind. | |
174 | let base_replacement = match extern_crate.orig_name { | |
175 | Some(orig_name) => format!("use {} as {};", orig_name, item.ident.name), | |
176 | None => format!("use {};", item.ident.name), | |
177 | }; | |
ba9703b0 XL |
178 | let vis = tcx.sess.source_map().span_to_snippet(item.vis.span).unwrap_or_default(); |
179 | let add_vis = |to| if vis.is_empty() { to } else { format!("{} {}", vis, to) }; | |
180 | lint.build("`extern crate` is not idiomatic in the new edition") | |
74b04a01 XL |
181 | .span_suggestion_short( |
182 | extern_crate.span, | |
ba9703b0 XL |
183 | &format!("convert it to a `{}`", add_vis("use".to_string())), |
184 | add_vis(base_replacement), | |
74b04a01 XL |
185 | Applicability::MachineApplicable, |
186 | ) | |
187 | .emit(); | |
188 | }) | |
476ff2be | 189 | } |
a7813a04 XL |
190 | } |
191 | ||
dc9dc135 XL |
192 | struct CollectExternCrateVisitor<'a, 'tcx> { |
193 | tcx: TyCtxt<'tcx>, | |
94b46f34 XL |
194 | crates_to_lint: &'a mut Vec<ExternCrateToLint>, |
195 | } | |
8bb4bdeb | 196 | |
94b46f34 | 197 | struct ExternCrateToLint { |
9fa01778 | 198 | /// `DefId` of the extern crate |
94b46f34 | 199 | def_id: DefId, |
3b2f2976 | 200 | |
94b46f34 XL |
201 | /// span from the item |
202 | span: Span, | |
203 | ||
204 | /// if `Some`, then this is renamed (`extern crate orig_name as | |
205 | /// crate_name`), and -- perhaps surprisingly -- this stores the | |
206 | /// *original* name (`item.name` will contain the new name) | |
207 | orig_name: Option<ast::Name>, | |
b7449926 XL |
208 | |
209 | /// if `false`, the original name started with `_`, so we shouldn't lint | |
210 | /// about it going unused (but we should still emit idiom lints). | |
211 | warn_if_unused: bool, | |
94b46f34 XL |
212 | } |
213 | ||
214 | impl<'a, 'tcx, 'v> ItemLikeVisitor<'v> for CollectExternCrateVisitor<'a, 'tcx> { | |
dfeec247 | 215 | fn visit_item(&mut self, item: &hir::Item<'_>) { |
e74abb32 | 216 | if let hir::ItemKind::ExternCrate(orig_name) = item.kind { |
416331ca | 217 | let extern_crate_def_id = self.tcx.hir().local_def_id(item.hir_id); |
dfeec247 XL |
218 | self.crates_to_lint.push(ExternCrateToLint { |
219 | def_id: extern_crate_def_id, | |
220 | span: item.span, | |
221 | orig_name, | |
222 | warn_if_unused: !item.ident.as_str().starts_with('_'), | |
223 | }); | |
ea8adc8c | 224 | } |
94b46f34 XL |
225 | } |
226 | ||
dfeec247 | 227 | fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem<'_>) {} |
94b46f34 | 228 | |
dfeec247 | 229 | fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem<'_>) {} |
a7813a04 | 230 | } |