Skip to content

Commit 90de61a

Browse files
committed
fix [std_instead_of_core]: rewrite to consider ListStems
1 parent 9116cc8 commit 90de61a

1 file changed

Lines changed: 238 additions & 99 deletions

File tree

clippy_lints/src/std_instead_of_core.rs

Lines changed: 238 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
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;
43
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};
1010
use rustc_session::impl_lint_pass;
1111
use rustc_span::symbol::kw;
1212
use rustc_span::{Span, sym};
@@ -93,129 +93,171 @@ impl_lint_pass!(StdReexports => [
9393
]);
9494

9595
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.
97101
msrv: Msrv,
98102
}
99103

100104
impl StdReexports {
101105
pub fn new(conf: &'static Conf) -> Self {
102106
Self {
103-
lint_points: Option::default(),
107+
lint_points: Vec::new(),
108+
item_context: Vec::new(),
104109
msrv: conf.msrv,
105110
}
106111
}
107112

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);
114131
}
115132
}
116133
}
117134

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>>>,
122139
}
123140

124141
impl<'tcx> LateLintPass<'tcx> for StdReexports {
125142
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+
}
151167

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+
}
157170
}
158171
}
159172

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);
163175

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+
}
166185
}
167186

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);
170189
}
171190
}
172191

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()));
193195

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();
206207

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,
210231
};
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+
}
219261
}
220262
}
221263

@@ -262,3 +304,100 @@ fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: Msrv) -> bool {
262304
}
263305
}
264306
}
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

Comments
 (0)