Skip to content

Commit de99588

Browse files
committed
fix [std_instead_of_core]: rewrite around check_item
1 parent ef124f6 commit de99588

7 files changed

Lines changed: 414 additions & 125 deletions

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ rustc_lint::late_lint_methods!(
757757
ManualRetain: manual_retain::ManualRetain = manual_retain::ManualRetain::new(conf),
758758
ManualRotate: manual_rotate::ManualRotate = manual_rotate::ManualRotate,
759759
Operators: operators::Operators = operators::Operators::new(conf),
760-
StdReexports: std_instead_of_core::StdReexports = std_instead_of_core::StdReexports::new(conf),
760+
StdReexports: std_instead_of_core::StdReexports<'tcx> = std_instead_of_core::StdReexports::new(conf),
761761
UncheckedTimeSubtraction: time_subtraction::UncheckedTimeSubtraction = time_subtraction::UncheckedTimeSubtraction::new(conf),
762762
PartialeqToNone: partialeq_to_none::PartialeqToNone = partialeq_to_none::PartialeqToNone,
763763
ManualAbsDiff: manual_abs_diff::ManualAbsDiff = manual_abs_diff::ManualAbsDiff::new(conf),
Lines changed: 118 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
use clippy_config::Conf;
2-
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
2+
use clippy_utils::diagnostics::span_lint_hir_and_then;
33
use clippy_utils::is_from_proc_macro;
44
use clippy_utils::msrvs::Msrv;
55
use rustc_errors::Applicability;
6-
use rustc_hir::def::{DefKind, Res};
76
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 as _};
7+
use rustc_hir::{Block, Body, HirId, Item, ItemKind, Path, PathSegment, StabilityLevel, StableSince};
8+
use rustc_lint::{LateContext, LateLintPass, LintContext as _};
109
use rustc_session::impl_lint_pass;
10+
use rustc_span::sym;
1111
use rustc_span::symbol::kw;
12-
use rustc_span::{Span, sym};
1312

1413
declare_clippy_lint! {
1514
/// ### What it does
@@ -86,144 +85,106 @@ declare_clippy_lint! {
8685
"type is imported from std when available in core"
8786
}
8887

89-
impl_lint_pass!(StdReexports => [
88+
impl_lint_pass!(StdReexports<'_> => [
9089
ALLOC_INSTEAD_OF_CORE,
9190
STD_INSTEAD_OF_ALLOC,
9291
STD_INSTEAD_OF_CORE,
9392
]);
9493

95-
pub struct StdReexports {
96-
lint_points: Option<(Span, Vec<LintPoint>)>,
94+
pub struct StdReexports<'a> {
95+
lint_points: Vec<LintPoint<'a>>,
9796
msrv: Msrv,
97+
use_item_depth: usize,
9898
}
9999

100-
impl StdReexports {
100+
impl<'a> StdReexports<'a> {
101101
pub fn new(conf: &'static Conf) -> Self {
102102
Self {
103-
lint_points: Option::default(),
103+
lint_points: Vec::new(),
104104
msrv: conf.msrv,
105-
}
106-
}
107-
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]))),
105+
use_item_depth: 0,
114106
}
115107
}
116108
}
117109

118-
#[derive(Debug)]
119-
enum LintPoint {
120-
Available(Span, &'static Lint, &'static str, &'static str),
121-
Conflict,
110+
#[derive(Debug, Clone, Copy)]
111+
struct LintPoint<'a> {
112+
root: PathSegment<'a>,
113+
tail: PathSegment<'a>,
122114
}
123115

124-
impl<'tcx> LateLintPass<'tcx> for StdReexports {
125-
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()
116+
impl<'tcx> LateLintPass<'tcx> for StdReexports<'tcx> {
117+
fn check_path(&mut self, _: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
118+
if self.use_item_depth == 0
119+
&& path.segments.len() > 1
120+
&& let Some(&root) = get_first_segment(path)
121+
&& let Some(&tail) = path.segments.last()
135122
{
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-
};
123+
self.lint_points.push(LintPoint {
124+
root,
125+
tail: PathSegment { res: path.res, ..tail },
126+
});
127+
}
128+
}
151129

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-
);
130+
fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
131+
if let ItemKind::Use(path, _use_kind) = item.kind {
132+
if path.segments.len() > 1
133+
&& let Some(&root) = get_first_segment(path)
134+
&& let Some(&tail) = path.segments.last()
135+
{
136+
for res in path.res.into_iter().flatten() {
137+
self.lint_points.push(LintPoint {
138+
root,
139+
tail: PathSegment { res, ..tail },
140+
});
141+
}
142+
}
143+
self.use_item_depth += 1;
144+
}
145+
}
146+
147+
fn check_item_post(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
148+
if matches!(item.kind, ItemKind::Use(_path, _use_kind)) {
149+
self.use_item_depth -= 1;
157150
}
158151
}
159152

160153
fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &Block<'tcx>) {
161-
emit_lints(cx, self.lint_points.take());
154+
emit_lints(cx, &mut self.lint_points, self.msrv);
162155
}
163156

164157
fn check_body_post(&mut self, cx: &LateContext<'tcx>, _: &Body<'tcx>) {
165-
emit_lints(cx, self.lint_points.take());
158+
emit_lints(cx, &mut self.lint_points, self.msrv);
166159
}
167160

168161
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
169-
emit_lints(cx, self.lint_points.take());
162+
emit_lints(cx, &mut self.lint_points, self.msrv);
170163
}
171164
}
172165

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-
};
166+
fn emit_lints(cx: &LateContext<'_>, lint_points: &mut Vec<LintPoint<'_>>, msrv: Msrv) {
167+
// This should already be the case, but we'll assert that it is
168+
lint_points.sort_by_key(|x| (x.root.ident.span, x.tail.ident.span));
177169

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-
},
170+
for shared_root in lint_points.chunk_by(|a, b| a.root.ident.span == b.root.ident.span) {
171+
if let Some(root) = shared_root.iter().find_map(|a| a.root.res.opt_def_id())
172+
&& !(root.is_crate_root() && LintPoint::try_emit(shared_root, cx, msrv, true, root))
173+
{
174+
for shared_tail in shared_root.chunk_by(|a, b| a.tail.ident.span == b.tail.ident.span) {
175+
LintPoint::try_emit(shared_tail, cx, msrv, false, root);
176+
}
191177
}
192178
}
193179

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-
}
206-
207-
for lint_point in lint_points {
208-
let LintPoint::Available(span, lint, used_mod, replace_with) = lint_point else {
209-
continue;
210-
};
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-
);
219-
}
180+
lint_points.clear();
220181
}
221182

222183
/// Returns the first named segment of a [`Path`].
223184
///
224185
/// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`]
225186
/// is returned.
226-
fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
187+
fn get_first_segment<'tcx, T>(path: &Path<'tcx, T>) -> Option<&'tcx PathSegment<'tcx>> {
227188
match path.segments {
228189
// A global path will have PathRoot as the first segment. In this case, return the segment after.
229190
[x, y, ..] if x.ident.name == kw::PathRoot => Some(y),
@@ -232,11 +193,12 @@ fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>>
232193
}
233194
}
234195

235-
/// Checks if all ancestors of `def_id` meet `msrv` to avoid linting [unstable moves](https://github.com/rust-lang/rust/pull/95956)
236-
/// or now stable moves that were once unstable.
196+
/// Checks if all ancestors of `def_id` meet `msrv` at `node` to avoid linting
197+
/// [unstable moves](https://github.com/rust-lang/rust/pull/95956) or now stable
198+
/// moves that were once unstable.
237199
///
238200
/// Does not catch individually moved items
239-
fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: Msrv) -> bool {
201+
fn is_stable_at(cx: &LateContext<'_>, mut def_id: DefId, msrv: Msrv, node: HirId) -> bool {
240202
loop {
241203
if let Some(stability) = cx.tcx.lookup_stability(def_id) {
242204
match stability.level {
@@ -247,8 +209,8 @@ fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: Msrv) -> bool {
247209
..
248210
} => return true,
249211
StabilityLevel::Stable { since, .. } => match since {
250-
StableSince::Version(v) if !msrv.meets(cx, v) => return false,
251-
StableSince::Current if msrv.current(cx).is_none() => return false,
212+
StableSince::Version(v) if !msrv.meets_at(cx.tcx, node, v) => return false,
213+
StableSince::Current if msrv.at(cx.tcx, node).is_none() => return false,
252214
StableSince::Err(_) => return false,
253215
StableSince::Version(_) | StableSince::Current => {},
254216
},
@@ -262,3 +224,53 @@ fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: Msrv) -> bool {
262224
}
263225
}
264226
}
227+
228+
impl<'a> LintPoint<'a> {
229+
fn try_emit(lint_points: &[Self], cx: &LateContext<'_>, msrv: Msrv, is_suggestion: bool, root: DefId) -> bool {
230+
if let Some(first) = lint_points.first()
231+
&& let Some(tail) = first.tail.res.opt_def_id()
232+
&& !first.root.ident.span.in_external_macro(cx.sess().source_map())
233+
&& !is_from_proc_macro(cx, &first.root.ident)
234+
&& lint_points
235+
.iter()
236+
.map(|lint_point| lint_point.tail.res.opt_def_id())
237+
.all(|id| id.is_some_and(|id| id.krate == tail.krate && is_stable_at(cx, id, msrv, first.tail.hir_id)))
238+
{
239+
let root = cx.tcx.crate_name(root.krate);
240+
let tail = cx.tcx.crate_name(tail.krate);
241+
242+
let (lint, message, help) = match (root, tail) {
243+
(sym::alloc, sym::core) => (
244+
ALLOC_INSTEAD_OF_CORE,
245+
"used import from `alloc` instead of `core`",
246+
"consider importing the item from `core`",
247+
),
248+
(sym::std, sym::alloc) => (
249+
STD_INSTEAD_OF_ALLOC,
250+
"used import from `std` instead of `alloc`",
251+
"consider importing the item from `alloc`",
252+
),
253+
(sym::std, sym::core) => (
254+
STD_INSTEAD_OF_CORE,
255+
"used import from `std` instead of `core`",
256+
"consider importing the item from `core`",
257+
),
258+
_ => return false,
259+
};
260+
261+
if is_suggestion {
262+
span_lint_hir_and_then(cx, lint, first.root.hir_id, first.root.ident.span, message, |diag| {
263+
diag.span_suggestion(first.root.ident.span, help, tail, Applicability::MachineApplicable);
264+
});
265+
} else {
266+
span_lint_hir_and_then(cx, lint, first.tail.hir_id, first.tail.ident.span, message, |diag| {
267+
diag.help(help);
268+
});
269+
}
270+
271+
true
272+
} else {
273+
false
274+
}
275+
}
276+
}

tests/ui/std_instead_of_core.fixed

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,6 @@ fn msrv_1_76(_: std::net::IpAddr) {}
8989
fn msrv_1_77(_: core::net::IpAddr) {}
9090
//~^ std_instead_of_core
9191

92-
#[warn(clippy::alloc_instead_of_core)]
93-
fn issue15579() {
94-
use std::alloc;
95-
96-
let layout = alloc::Layout::new::<u8>();
97-
}
98-
9992
#[warn(clippy::std_instead_of_core)]
10093
fn issue13158_core_io() {
10194
// items moved from std::io into core::io are stable in an unstable module.
@@ -115,3 +108,26 @@ fn issue13158_msrv_1_80(_: &dyn std::error::Error) {}
115108
#[clippy::msrv = "1.81"]
116109
fn issue13158_msrv_1_81(_: &dyn core::error::Error) {}
117110
//~^ std_instead_of_core
111+
112+
#[warn(clippy::std_instead_of_core)]
113+
fn issue17260() {
114+
use core::concat;
115+
//~^ std_instead_of_core
116+
}
117+
118+
#[warn(clippy::std_instead_of_alloc)]
119+
fn non_use_paths() {
120+
type Foo = alloc::sync::Arc<std::sync::Mutex<bool>>;
121+
//~^ std_instead_of_alloc
122+
123+
let _x = core::iter::repeat(u8::default());
124+
//~^ std_instead_of_core
125+
126+
fn impl_trait(_: impl core::fmt::Display) {}
127+
//~^ std_instead_of_core
128+
129+
struct Bar {
130+
layout: core::alloc::Layout,
131+
//~^ std_instead_of_core
132+
}
133+
}

tests/ui/std_instead_of_core.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,6 @@ fn msrv_1_76(_: std::net::IpAddr) {}
8989
fn msrv_1_77(_: std::net::IpAddr) {}
9090
//~^ std_instead_of_core
9191

92-
#[warn(clippy::alloc_instead_of_core)]
93-
fn issue15579() {
94-
use std::alloc;
95-
96-
let layout = alloc::Layout::new::<u8>();
97-
}
98-
9992
#[warn(clippy::std_instead_of_core)]
10093
fn issue13158_core_io() {
10194
// items moved from std::io into core::io are stable in an unstable module.
@@ -115,3 +108,26 @@ fn issue13158_msrv_1_80(_: &dyn std::error::Error) {}
115108
#[clippy::msrv = "1.81"]
116109
fn issue13158_msrv_1_81(_: &dyn std::error::Error) {}
117110
//~^ std_instead_of_core
111+
112+
#[warn(clippy::std_instead_of_core)]
113+
fn issue17260() {
114+
use std::concat;
115+
//~^ std_instead_of_core
116+
}
117+
118+
#[warn(clippy::std_instead_of_alloc)]
119+
fn non_use_paths() {
120+
type Foo = std::sync::Arc<std::sync::Mutex<bool>>;
121+
//~^ std_instead_of_alloc
122+
123+
let _x = std::iter::repeat(u8::default());
124+
//~^ std_instead_of_core
125+
126+
fn impl_trait(_: impl std::fmt::Display) {}
127+
//~^ std_instead_of_core
128+
129+
struct Bar {
130+
layout: std::alloc::Layout,
131+
//~^ std_instead_of_core
132+
}
133+
}

0 commit comments

Comments
 (0)