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