Skip to content

Commit 2eda7d5

Browse files
committed
fix: [std_instead_of_core] false positive for multi-imports
1 parent 3dcef78 commit 2eda7d5

6 files changed

Lines changed: 274 additions & 49 deletions

clippy_lints/src/std_instead_of_core.rs

Lines changed: 126 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ use clippy_config::Conf;
22
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
33
use clippy_utils::is_from_proc_macro;
44
use clippy_utils::msrvs::Msrv;
5-
use rustc_errors::Applicability;
5+
use rustc_errors::{Applicability, MultiSpan};
66
use rustc_hir::def::{DefKind, Res};
77
use rustc_hir::def_id::DefId;
88
use rustc_hir::{Block, Body, HirId, Path, PathSegment, StabilityLevel, StableSince};
9-
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
9+
use rustc_lint::{LateContext, LateLintPass, LintContext};
1010
use rustc_session::impl_lint_pass;
1111
use rustc_span::symbol::kw;
12-
use rustc_span::{Span, sym};
12+
use rustc_span::{Span, Symbol, sym};
1313

1414
declare_clippy_lint! {
1515
/// ### What it does
@@ -116,44 +116,34 @@ impl StdReexports {
116116
}
117117

118118
#[derive(Debug)]
119-
enum LintPoint {
120-
Available(Span, &'static Lint, &'static str, &'static str),
121-
Conflict,
119+
struct LintPoint {
120+
span: Span,
121+
used_from: Symbol,
122+
available_from_core: bool,
123+
available_from_alloc: bool,
122124
}
123125

124126
impl<'tcx> LateLintPass<'tcx> for StdReexports {
125127
fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
126128
if let Res::Def(def_kind, def_id) = path.res
127129
&& let Some(first_segment) = get_first_segment(path)
128-
&& is_stable(cx, def_id, self.msrv)
129130
&& !path.span.in_external_macro(cx.sess().source_map())
130131
&& !is_from_proc_macro(cx, &first_segment.ident)
131132
&& !matches!(def_kind, DefKind::Macro(_))
132133
&& let Some(last_segment) = path.segments.last()
133134
&& let Res::Def(DefKind::Mod, crate_def_id) = first_segment.res
134135
&& crate_def_id.is_crate_root()
135136
{
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-
},
137+
let is_stable = is_stable(cx, def_id, self.msrv);
138+
139+
let lint_point = LintPoint {
140+
span: last_segment.ident.span,
141+
used_from: first_segment.ident.name,
142+
available_from_core: is_stable && is_available_from(cx, sym::core, path),
143+
available_from_alloc: is_stable && is_available_from(cx, sym::alloc, path),
150144
};
151145

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-
);
146+
self.lint_if_finish(cx, first_segment.ident.span, lint_point);
157147
}
158148
}
159149

@@ -175,23 +165,55 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
175165
return;
176166
};
177167

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-
},
168+
let mut core_span = MultiSpan::new();
169+
let mut alloc_span = MultiSpan::new();
170+
let mut all_from_std = true;
171+
let mut all_from_alloc = true;
172+
let mut all_core = true;
173+
let mut all_alloc = true;
174+
175+
for lint_point in lint_points {
176+
all_from_std &= lint_point.used_from == sym::std;
177+
all_from_alloc &= lint_point.used_from == sym::alloc;
178+
179+
if lint_point.available_from_core {
180+
core_span.push_primary_span(lint_point.span);
181+
} else {
182+
all_core = false;
183+
}
184+
185+
if !lint_point.available_from_alloc {
186+
all_alloc = false;
187+
}
188+
189+
if !lint_point.available_from_core && lint_point.available_from_alloc {
190+
alloc_span.push_primary_span(lint_point.span);
191191
}
192192
}
193193

194-
if !has_conflict && let Some((lint, used_mod, replace_with)) = lint {
194+
let mut helps = Vec::new();
195+
let mut suggestions = Vec::new();
196+
197+
if all_from_std {
198+
if all_core {
199+
suggestions.push((STD_INSTEAD_OF_CORE, &sym::std, &sym::core));
200+
helps.push((STD_INSTEAD_OF_ALLOC, &sym::std, &sym::alloc, alloc_span));
201+
} else if all_alloc {
202+
suggestions.push((STD_INSTEAD_OF_ALLOC, &sym::std, &sym::alloc));
203+
helps.push((STD_INSTEAD_OF_CORE, &sym::std, &sym::core, core_span));
204+
} else {
205+
helps.push((STD_INSTEAD_OF_CORE, &sym::std, &sym::core, core_span));
206+
helps.push((STD_INSTEAD_OF_ALLOC, &sym::std, &sym::alloc, alloc_span));
207+
}
208+
} else if all_from_alloc {
209+
if all_core {
210+
suggestions.push((ALLOC_INSTEAD_OF_CORE, &sym::alloc, &sym::core));
211+
} else {
212+
helps.push((ALLOC_INSTEAD_OF_CORE, &sym::alloc, &sym::core, core_span));
213+
}
214+
}
215+
216+
for (lint, used_mod, replace_with) in suggestions {
195217
span_lint_and_sugg(
196218
cx,
197219
lint,
@@ -201,13 +223,12 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
201223
(*replace_with).to_string(),
202224
Applicability::MachineApplicable,
203225
);
204-
return;
205226
}
206227

207-
for lint_point in lint_points {
208-
let LintPoint::Available(span, lint, used_mod, replace_with) = lint_point else {
228+
for (lint, used_mod, replace_with, span) in helps {
229+
if span.primary_spans().is_empty() {
209230
continue;
210-
};
231+
}
211232
span_lint_and_help(
212233
cx,
213234
lint,
@@ -219,6 +240,67 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
219240
}
220241
}
221242

243+
/// Checks if the provided `path` could have its crate replaced with `krate` and still be valid.
244+
fn is_available_from(cx: &LateContext<'_>, krate: Symbol, path: &Path<'_>) -> bool {
245+
// We're attempting to find this DefId with an alternate path.
246+
// If this DefId isn't available then there's no way to do that.
247+
let Some(target) = path.res.opt_def_id() else {
248+
return false;
249+
};
250+
251+
// Determine DefId for krate
252+
let Some(krate) = cx
253+
.tcx
254+
.crates(())
255+
.iter()
256+
.filter(|&&krate| cx.tcx.is_user_visible_dep(krate))
257+
.find(|&&krate_num| cx.tcx.crate_name(krate_num) == krate)
258+
.map(|&krate| DefId {
259+
index: rustc_span::def_id::CRATE_DEF_INDEX,
260+
krate,
261+
})
262+
else {
263+
return false;
264+
};
265+
266+
path.segments
267+
.iter()
268+
.skip_while(|segment| {
269+
// Skip `::crate` and `crate` segments
270+
segment.ident.name == kw::PathRoot || segment.res.opt_def_id().is_some_and(DefId::is_crate_root)
271+
})
272+
.scan(krate, |cursor, segment| {
273+
// Some, but not all, items from core and reexported by alloc.
274+
// To find these cases, we must walk the alloc and core crates along the provided path
275+
// to see if the desired item is accessible.
276+
*cursor = cx
277+
.tcx
278+
.module_children(*cursor)
279+
.iter()
280+
.filter(|child| child.vis.is_public())
281+
.filter(|child| {
282+
// We search by name to allow cases like `core::str` and `std::str`.
283+
// `std::str` is actually a reexport of `alloc::str`, but `alloc::str`
284+
// reexports the contents of `core::str`, so many instances of `std::str`
285+
// can be replaced with `core::str`, even if they are distinct items.
286+
child.ident.name == segment.ident.name
287+
})
288+
.find_map(|child| {
289+
// TyCtxt::module_children will ICE if the provided DefId isn't "module like".
290+
// The initial value will always be a crate so no need to check there.
291+
// Since what we're searching for may not be "module like", we must
292+
// exempt a non-"module like" DefId if and only if it is our target.
293+
let is_module_like = child.res.module_like_def_id().is_some();
294+
let def_id = child.res.opt_def_id()?;
295+
(def_id == target || is_module_like).then_some(def_id)
296+
})?;
297+
298+
Some(*cursor)
299+
})
300+
.last()
301+
.is_some_and(|cursor| cursor == target)
302+
}
303+
222304
/// Returns the first named segment of a [`Path`].
223305
///
224306
/// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`]

tests/ui/std_instead_of_core.fixed

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,20 @@ fn issue15579() {
9696

9797
let layout = alloc::Layout::new::<u8>();
9898
}
99+
100+
#[warn(clippy::std_instead_of_alloc)]
101+
fn pr17252() {
102+
use core::result::{self, Iter, Result};
103+
//~^ std_instead_of_core
104+
105+
use alloc::str::{self as _};
106+
//~^ std_instead_of_alloc
107+
108+
use alloc::str::{self as _, FromStr as _};
109+
//~^ std_instead_of_alloc
110+
//~| std_instead_of_core
111+
112+
use alloc::fmt::{self, Write};
113+
//~^ std_instead_of_alloc
114+
//~| std_instead_of_core
115+
}

tests/ui/std_instead_of_core.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,20 @@ fn issue15579() {
9696

9797
let layout = alloc::Layout::new::<u8>();
9898
}
99+
100+
#[warn(clippy::std_instead_of_alloc)]
101+
fn pr17252() {
102+
use std::result::{self, Iter, Result};
103+
//~^ std_instead_of_core
104+
105+
use std::str::{self as _};
106+
//~^ std_instead_of_alloc
107+
108+
use std::str::{self as _, FromStr as _};
109+
//~^ std_instead_of_alloc
110+
//~| std_instead_of_core
111+
112+
use std::fmt::{self, Write};
113+
//~^ std_instead_of_alloc
114+
//~| std_instead_of_core
115+
}

tests/ui/std_instead_of_core.stderr

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,45 @@ error: used import from `std` instead of `core`
9797
LL | fn msrv_1_77(_: std::net::IpAddr) {}
9898
| ^^^ help: consider importing the item from `core`: `core`
9999

100-
error: aborting due to 15 previous errors
100+
error: used import from `std` instead of `core`
101+
--> tests/ui/std_instead_of_core.rs:102:9
102+
|
103+
LL | use std::result::{self, Iter, Result};
104+
| ^^^ help: consider importing the item from `core`: `core`
105+
106+
error: used import from `std` instead of `alloc`
107+
--> tests/ui/std_instead_of_core.rs:105:9
108+
|
109+
LL | use std::str::{self as _};
110+
| ^^^ help: consider importing the item from `alloc`: `alloc`
111+
112+
error: used import from `std` instead of `alloc`
113+
--> tests/ui/std_instead_of_core.rs:108:9
114+
|
115+
LL | use std::str::{self as _, FromStr as _};
116+
| ^^^ help: consider importing the item from `alloc`: `alloc`
117+
118+
error: used import from `std` instead of `core`
119+
--> tests/ui/std_instead_of_core.rs:108:31
120+
|
121+
LL | use std::str::{self as _, FromStr as _};
122+
| ^^^^^^^
123+
|
124+
= help: consider importing the item from `core`
125+
126+
error: used import from `std` instead of `alloc`
127+
--> tests/ui/std_instead_of_core.rs:112:9
128+
|
129+
LL | use std::fmt::{self, Write};
130+
| ^^^ help: consider importing the item from `alloc`: `alloc`
131+
132+
error: used import from `std` instead of `core`
133+
--> tests/ui/std_instead_of_core.rs:112:26
134+
|
135+
LL | use std::fmt::{self, Write};
136+
| ^^^^^
137+
|
138+
= help: consider importing the item from `core`
139+
140+
error: aborting due to 21 previous errors
101141

tests/ui/std_instead_of_core_unfixable.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![warn(clippy::std_instead_of_core)]
22
#![warn(clippy::std_instead_of_alloc)]
3+
#![warn(clippy::alloc_instead_of_core)]
34
#![allow(unused_imports)]
45

56
#[rustfmt::skip]
@@ -13,4 +14,31 @@ fn issue15143() {
1314
use std::{error::Error, vec::Vec, fs::File};
1415
//~^ std_instead_of_core
1516
//~| std_instead_of_alloc
17+
18+
use std::sync::{
19+
Arc,
20+
Mutex,
21+
Weak,
22+
atomic::{
23+
AtomicPtr,
24+
Ordering,
25+
},
26+
};
27+
//~^^^^^^^^ std_instead_of_alloc
28+
//~^^^^^ std_instead_of_core
29+
}
30+
31+
#[rustfmt::skip]
32+
fn pr16964() {
33+
use std::{
34+
borrow::Cow,
35+
collections::BTreeSet,
36+
ffi::OsString,
37+
};
38+
//~^^^^ std_instead_of_alloc
39+
40+
extern crate alloc;
41+
42+
use alloc::str::{self as _, FromStr as _};
43+
//~^ alloc_instead_of_core
1644
}

0 commit comments

Comments
 (0)