|
1 | 1 | use clippy_config::Conf; |
2 | | -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; |
3 | | -use clippy_utils::is_from_proc_macro; |
| 2 | +use clippy_utils::diagnostics::span_lint_and_then; |
4 | 3 | use clippy_utils::msrvs::Msrv; |
5 | | -use rustc_errors::Applicability; |
6 | | -use rustc_hir::def::{DefKind, Res}; |
7 | | -use rustc_hir::def_id::DefId; |
8 | | -use rustc_hir::{Block, Body, HirId, Path, PathSegment, StabilityLevel, StableSince}; |
9 | | -use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; |
| 4 | +use itertools::Itertools; |
| 5 | +use rustc_errors::{Applicability, Diag}; |
| 6 | +use rustc_hir::def::{DefKind, PerNS, Res}; |
| 7 | +use rustc_hir::def_id::{CrateNum, DefId}; |
| 8 | +use rustc_hir::{HirId, Item, ItemKind, Path, PathSegment, StabilityLevel, StableSince, UseKind}; |
| 9 | +use rustc_lint::{LateContext, LateLintPass, LintContext}; |
10 | 10 | use rustc_session::impl_lint_pass; |
11 | 11 | use rustc_span::symbol::kw; |
12 | 12 | use rustc_span::{Span, sym}; |
@@ -93,129 +93,171 @@ impl_lint_pass!(StdReexports => [ |
93 | 93 | ]); |
94 | 94 |
|
95 | 95 | pub struct StdReexports { |
96 | | - lint_points: Option<(Span, Vec<LintPoint>)>, |
| 96 | + /// Paths which could be candidates for linting. |
| 97 | + lint_points: Vec<LintPoint>, |
| 98 | + /// Tracks nesting when linting a multi-import `use` statement. |
| 99 | + item_context: Vec<ItemContext>, |
| 100 | + /// Current Minimum Supported Rust Version. |
97 | 101 | msrv: Msrv, |
98 | 102 | } |
99 | 103 |
|
100 | 104 | impl StdReexports { |
101 | 105 | pub fn new(conf: &'static Conf) -> Self { |
102 | 106 | Self { |
103 | | - lint_points: Option::default(), |
| 107 | + lint_points: Vec::new(), |
| 108 | + item_context: Vec::new(), |
104 | 109 | msrv: conf.msrv, |
105 | 110 | } |
106 | 111 | } |
107 | 112 |
|
108 | | - fn lint_if_finish(&mut self, cx: &LateContext<'_>, krate: Span, lint_point: LintPoint) { |
109 | | - match &mut self.lint_points { |
110 | | - Some((prev_krate, prev_lints)) if prev_krate.overlaps(krate) => { |
111 | | - prev_lints.push(lint_point); |
112 | | - }, |
113 | | - _ => emit_lints(cx, self.lint_points.replace((krate, vec![lint_point]))), |
| 113 | + fn lint_if_finish(&mut self, cx: &LateContext<'_>, item: Span) { |
| 114 | + while let Some(top) = self.item_context.pop_if(|top| !top.span.contains(item)) { |
| 115 | + let count = match LintPoint::merge_top(&self.lint_points, &top) { |
| 116 | + Some(merged) => { |
| 117 | + self.lint_points.truncate(self.lint_points.len() - top.lint_points); |
| 118 | + self.lint_points.push(merged); |
| 119 | + 1 |
| 120 | + }, |
| 121 | + None => top.lint_points, |
| 122 | + }; |
| 123 | + |
| 124 | + if let Some(next_top) = self.item_context.last_mut() { |
| 125 | + next_top.lint_points += count; |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + if self.item_context.is_empty() { |
| 130 | + emit_lints(cx, self); |
114 | 131 | } |
115 | 132 | } |
116 | 133 | } |
117 | 134 |
|
118 | | -#[derive(Debug)] |
119 | | -enum LintPoint { |
120 | | - Available(Span, &'static Lint, &'static str, &'static str), |
121 | | - Conflict, |
| 135 | +#[derive(Clone, Copy, Debug)] |
| 136 | +struct LintPoint { |
| 137 | + first: Path<'static, DefId>, |
| 138 | + last: Path<'static, PerNS<Option<DefId>>>, |
122 | 139 | } |
123 | 140 |
|
124 | 141 | impl<'tcx> LateLintPass<'tcx> for StdReexports { |
125 | 142 | fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) { |
126 | | - if let Res::Def(def_kind, def_id) = path.res |
127 | | - && let Some(first_segment) = get_first_segment(path) |
128 | | - && is_stable(cx, def_id, self.msrv) |
129 | | - && !path.span.in_external_macro(cx.sess().source_map()) |
130 | | - && !is_from_proc_macro(cx, &first_segment.ident) |
131 | | - && !matches!(def_kind, DefKind::Macro(_)) |
132 | | - && let Some(last_segment) = path.segments.last() |
133 | | - && let Res::Def(DefKind::Mod, crate_def_id) = first_segment.res |
134 | | - && crate_def_id.is_crate_root() |
135 | | - { |
136 | | - let (lint, used_mod, replace_with) = match first_segment.ident.name { |
137 | | - sym::std => match cx.tcx.crate_name(def_id.krate) { |
138 | | - sym::core => (STD_INSTEAD_OF_CORE, "std", "core"), |
139 | | - sym::alloc => (STD_INSTEAD_OF_ALLOC, "std", "alloc"), |
140 | | - _ => { |
141 | | - self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict); |
142 | | - return; |
143 | | - }, |
144 | | - }, |
145 | | - sym::alloc if cx.tcx.crate_name(def_id.krate) == sym::core => (ALLOC_INSTEAD_OF_CORE, "alloc", "core"), |
146 | | - _ => { |
147 | | - self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict); |
148 | | - return; |
149 | | - }, |
150 | | - }; |
| 143 | + if let Some(lint_point) = LintPoint::try_from_path(path) { |
| 144 | + if let Some(last) = self.lint_points.last_mut() |
| 145 | + && last.last.span == lint_point.last.span |
| 146 | + { |
| 147 | + last.last.res = PerNS { |
| 148 | + value_ns: last.last.res.value_ns.or(lint_point.last.res.value_ns), |
| 149 | + type_ns: last.last.res.type_ns.or(lint_point.last.res.type_ns), |
| 150 | + macro_ns: last.last.res.macro_ns.or(lint_point.last.res.macro_ns), |
| 151 | + }; |
| 152 | + } else { |
| 153 | + if let Some(top) = self.item_context.last_mut() |
| 154 | + && top.span.contains(lint_point.last.span) |
| 155 | + { |
| 156 | + top.lint_points += 1; |
| 157 | + } else { |
| 158 | + let span = path |
| 159 | + .segments |
| 160 | + .iter() |
| 161 | + .map(|s| s.ident.span) |
| 162 | + .reduce(Span::to) |
| 163 | + .unwrap_or(path.span); |
| 164 | + self.lint_if_finish(cx, span); |
| 165 | + self.item_context.push(ItemContext { span, lint_points: 1 }); |
| 166 | + } |
151 | 167 |
|
152 | | - self.lint_if_finish( |
153 | | - cx, |
154 | | - first_segment.ident.span, |
155 | | - LintPoint::Available(last_segment.ident.span, lint, used_mod, replace_with), |
156 | | - ); |
| 168 | + self.lint_points.push(lint_point); |
| 169 | + } |
157 | 170 | } |
158 | 171 | } |
159 | 172 |
|
160 | | - fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &Block<'tcx>) { |
161 | | - emit_lints(cx, self.lint_points.take()); |
162 | | - } |
| 173 | + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { |
| 174 | + self.lint_if_finish(cx, item.span); |
163 | 175 |
|
164 | | - fn check_body_post(&mut self, cx: &LateContext<'tcx>, _: &Body<'tcx>) { |
165 | | - emit_lints(cx, self.lint_points.take()); |
| 176 | + match item.kind { |
| 177 | + ItemKind::Use(_path, UseKind::ListStem) => { |
| 178 | + self.item_context.push(ItemContext { |
| 179 | + span: item.span, |
| 180 | + lint_points: 0, |
| 181 | + }); |
| 182 | + }, |
| 183 | + _ => {}, |
| 184 | + } |
166 | 185 | } |
167 | 186 |
|
168 | | - fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { |
169 | | - emit_lints(cx, self.lint_points.take()); |
| 187 | + fn check_item_post(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { |
| 188 | + self.lint_if_finish(cx, item.span); |
170 | 189 | } |
171 | 190 | } |
172 | 191 |
|
173 | | -fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>) { |
174 | | - let Some((krate_span, lint_points)) = lint_points else { |
175 | | - return; |
176 | | - }; |
177 | | - |
178 | | - let mut lint: Option<(&'static Lint, &'static str, &'static str)> = None; |
179 | | - let mut has_conflict = false; |
180 | | - for lint_point in &lint_points { |
181 | | - match lint_point { |
182 | | - LintPoint::Available(_, l, used_mod, replace_with) |
183 | | - if lint.is_none_or(|(prev_l, ..)| l.name == prev_l.name) => |
184 | | - { |
185 | | - lint = Some((l, used_mod, replace_with)); |
186 | | - }, |
187 | | - _ => { |
188 | | - has_conflict = true; |
189 | | - break; |
190 | | - }, |
191 | | - } |
192 | | - } |
| 192 | +fn emit_lints(cx: &LateContext<'_>, this: &mut StdReexports) { |
| 193 | + this.lint_points |
| 194 | + .sort_by_key(|path| (path.first.span, path.first.res.krate, path.defined_in())); |
193 | 195 |
|
194 | | - if !has_conflict && let Some((lint, used_mod, replace_with)) = lint { |
195 | | - span_lint_and_sugg( |
196 | | - cx, |
197 | | - lint, |
198 | | - krate_span, |
199 | | - format!("used import from `{used_mod}` instead of `{replace_with}`"), |
200 | | - format!("consider importing the item from `{replace_with}`"), |
201 | | - (*replace_with).to_string(), |
202 | | - Applicability::MachineApplicable, |
203 | | - ); |
204 | | - return; |
205 | | - } |
| 196 | + let mut drain = this |
| 197 | + .lint_points |
| 198 | + .drain(..) |
| 199 | + .filter(|p| p.should_emit(cx, this.msrv)) |
| 200 | + .peekable(); |
| 201 | + |
| 202 | + while let Some(path) = drain.next() { |
| 203 | + let mut spans = drain |
| 204 | + .peeking_take_while(|other| path.compatible_with(other)) |
| 205 | + .map(|other| other.last.span) |
| 206 | + .peekable(); |
206 | 207 |
|
207 | | - for lint_point in lint_points { |
208 | | - let LintPoint::Available(span, lint, used_mod, replace_with) = lint_point else { |
209 | | - continue; |
| 208 | + let used_from = cx.tcx.crate_name(path.first.res.krate); |
| 209 | + let defined_in = cx.tcx.crate_name(path.defined_in()); |
| 210 | + |
| 211 | + let (suggestion, lint, message, help) = match (used_from, defined_in) { |
| 212 | + (sym::std, sym::core) => ( |
| 213 | + Some(sym::core), |
| 214 | + STD_INSTEAD_OF_CORE, |
| 215 | + "used import from `std` instead of `core`", |
| 216 | + "consider importing the item from `core`", |
| 217 | + ), |
| 218 | + (sym::std, sym::alloc) => ( |
| 219 | + Some(sym::alloc), |
| 220 | + STD_INSTEAD_OF_ALLOC, |
| 221 | + "used import from `std` instead of `alloc`", |
| 222 | + "consider importing the item from `alloc`", |
| 223 | + ), |
| 224 | + (sym::alloc, sym::core) => ( |
| 225 | + Some(sym::core), |
| 226 | + ALLOC_INSTEAD_OF_CORE, |
| 227 | + "used import from `alloc` instead of `core`", |
| 228 | + "consider importing the item from `core`", |
| 229 | + ), |
| 230 | + _ => continue, |
210 | 231 | }; |
211 | | - span_lint_and_help( |
212 | | - cx, |
213 | | - lint, |
214 | | - span, |
215 | | - format!("used import from `{used_mod}` instead of `{replace_with}`"), |
216 | | - None, |
217 | | - format!("consider importing the item from `{replace_with}`"), |
218 | | - ); |
| 232 | + |
| 233 | + let should_suggest = path.last.span.contains(path.first.span) && path.first.res.is_crate_root(); |
| 234 | + |
| 235 | + let then = |diag: &mut Diag<'_, ()>| { |
| 236 | + if should_suggest { |
| 237 | + if let Some(suggestion) = suggestion { |
| 238 | + diag.span_suggestion(path.first.span, help, suggestion, Applicability::MaybeIncorrect); |
| 239 | + } else { |
| 240 | + diag.help(help); |
| 241 | + diag.help("consider adding an `extern crate` statement at the crate root"); |
| 242 | + } |
| 243 | + } else { |
| 244 | + diag.help(help); |
| 245 | + } |
| 246 | + }; |
| 247 | + |
| 248 | + if spans.peek().is_none() { |
| 249 | + let span = if should_suggest { |
| 250 | + path.first.span |
| 251 | + } else { |
| 252 | + path.last.span |
| 253 | + }; |
| 254 | + |
| 255 | + span_lint_and_then(cx, lint, span, message, then); |
| 256 | + } else { |
| 257 | + for span in core::iter::once(path.last.span).chain(spans) { |
| 258 | + span_lint_and_then(cx, lint, span, message, then); |
| 259 | + } |
| 260 | + } |
219 | 261 | } |
220 | 262 | } |
221 | 263 |
|
@@ -262,3 +304,100 @@ fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: Msrv) -> bool { |
262 | 304 | } |
263 | 305 | } |
264 | 306 | } |
| 307 | + |
| 308 | +impl LintPoint { |
| 309 | + fn try_from_path(path: &Path<'_>) -> Option<Self> { |
| 310 | + let first = get_first_segment(path)?; |
| 311 | + let last = path.segments.last()?; |
| 312 | + |
| 313 | + if !matches!(first.res, Res::Def(DefKind::Mod, _)) { |
| 314 | + return None; |
| 315 | + } |
| 316 | + |
| 317 | + Some(LintPoint { |
| 318 | + first: Path { |
| 319 | + span: first.ident.span, |
| 320 | + res: first.res.opt_def_id()?, |
| 321 | + segments: &[], |
| 322 | + }, |
| 323 | + last: Path { |
| 324 | + span: last.ident.span, |
| 325 | + res: { |
| 326 | + let mut res = PerNS::default(); |
| 327 | + res[path.res.ns()?] = Some(path.res.opt_def_id()?); |
| 328 | + res |
| 329 | + }, |
| 330 | + segments: &[], |
| 331 | + }, |
| 332 | + }) |
| 333 | + } |
| 334 | + |
| 335 | + /// Indicates that two [`LintPoint`]s could be merged. |
| 336 | + fn compatible_with(&self, other: &Self) -> bool { |
| 337 | + self.first.span == other.first.span |
| 338 | + && self.first.res == other.first.res |
| 339 | + && self |
| 340 | + .last |
| 341 | + .res |
| 342 | + .present_items() |
| 343 | + .all(|a| other.last.res.present_items().all(|b| a.krate == b.krate)) |
| 344 | + } |
| 345 | + |
| 346 | + /// Indicates this [`LintPoint`] should be emitted to the user. |
| 347 | + fn should_emit(&self, cx: &LateContext<'_>, msrv: Msrv) -> bool { |
| 348 | + // FIXME(#11159): Delete this. |
| 349 | + if !self.first.res.is_crate_root() { |
| 350 | + return false; |
| 351 | + } |
| 352 | + |
| 353 | + // FIXME(#17260): Delete this. |
| 354 | + if self.last.res.value_ns.is_none() && self.last.res.type_ns.is_none() && self.last.res.macro_ns.is_some() { |
| 355 | + return false; |
| 356 | + } |
| 357 | + |
| 358 | + // NOTE: |
| 359 | + // Consider using `self.first.span.can_be_used_for_suggestions()` |
| 360 | + |
| 361 | + !self.first.span.in_external_macro(cx.sess().source_map()) |
| 362 | + && self |
| 363 | + .last |
| 364 | + .res |
| 365 | + .present_items() |
| 366 | + .all(|a| is_stable(cx, a, msrv) && self.first.res.krate != a.krate && self.defined_in() == a.krate) |
| 367 | + } |
| 368 | + |
| 369 | + fn defined_in(&self) -> CrateNum { |
| 370 | + self.last |
| 371 | + .res |
| 372 | + .present_items() |
| 373 | + .next() |
| 374 | + .expect("LintPoint only created if at least one Namespace resolved") |
| 375 | + .krate |
| 376 | + } |
| 377 | + |
| 378 | + fn merge_top(lint_points: &[Self], top: &ItemContext) -> Option<Self> { |
| 379 | + lint_points |
| 380 | + .iter() |
| 381 | + .rev() |
| 382 | + .take(top.lint_points) |
| 383 | + .try_fold(Option::<&Self>::None, |a, b| match a { |
| 384 | + Some(a) if a.compatible_with(b) => Some(Some(a)), |
| 385 | + None => Some(Some(b)), |
| 386 | + _ => None, |
| 387 | + }) |
| 388 | + .flatten() |
| 389 | + .map(|representative| Self { |
| 390 | + last: Path { |
| 391 | + span: top.span, |
| 392 | + ..representative.last |
| 393 | + }, |
| 394 | + ..*representative |
| 395 | + }) |
| 396 | + } |
| 397 | +} |
| 398 | + |
| 399 | +#[derive(Debug)] |
| 400 | +struct ItemContext { |
| 401 | + span: Span, |
| 402 | + lint_points: usize, |
| 403 | +} |
0 commit comments