Skip to content

Commit 5e134b4

Browse files
committed
fix: [std_instead_of_core] false positive for multi-imports
1 parent a6b9266 commit 5e134b4

6 files changed

Lines changed: 260 additions & 53 deletions

clippy_lints/src/std_instead_of_core.rs

Lines changed: 113 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ 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;
6-
use rustc_hir::def::{DefKind, Res};
5+
use clippy_utils::paths::{PathNS, lookup_path};
6+
use rustc_errors::{Applicability, MultiSpan};
7+
use rustc_hir::def::{DefKind, Namespace, Res};
78
use rustc_hir::def_id::DefId;
89
use rustc_hir::{Block, Body, HirId, Path, PathSegment, StabilityLevel, StableSince};
9-
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
10+
use rustc_lint::{LateContext, LateLintPass, LintContext};
1011
use rustc_session::impl_lint_pass;
1112
use rustc_span::symbol::kw;
12-
use rustc_span::{Span, sym};
13+
use rustc_span::{Span, Symbol, sym};
1314

1415
declare_clippy_lint! {
1516
/// ### What it does
@@ -116,43 +117,55 @@ impl StdReexports {
116117
}
117118

118119
#[derive(Debug)]
119-
enum LintPoint {
120-
Available(Span, &'static Lint, &'static str, &'static str),
121-
Conflict,
120+
struct LintPoint {
121+
span: Span,
122+
used_from: Symbol,
123+
is_stable: bool,
124+
available_from_core: bool,
125+
available_from_alloc: bool,
122126
}
123127

124128
impl<'tcx> LateLintPass<'tcx> for StdReexports {
125129
fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
126130
if let Res::Def(def_kind, def_id) = path.res
127131
&& let Some(first_segment) = get_first_segment(path)
128-
&& is_stable(cx, def_id, self.msrv)
129132
&& !path.span.in_external_macro(cx.sess().source_map())
130133
&& !is_from_proc_macro(cx, &first_segment.ident)
131134
&& !matches!(def_kind, DefKind::Macro(_))
132135
&& let Some(last_segment) = path.segments.last()
133136
&& let Res::Def(DefKind::Mod, crate_def_id) = first_segment.res
134137
&& crate_def_id.is_crate_root()
135138
{
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-
},
139+
let namespace = match def_kind.ns() {
140+
Some(Namespace::TypeNS) => PathNS::Type,
141+
Some(Namespace::ValueNS) => PathNS::Value,
142+
Some(Namespace::MacroNS) => PathNS::Macro,
143+
None => PathNS::Arbitrary,
150144
};
151145

146+
let mut path_new = path
147+
.segments
148+
.iter()
149+
.map(|segment| segment.ident.name)
150+
.skip_while(|&segment| segment == kw::PathRoot)
151+
.collect::<Vec<_>>();
152+
153+
path_new[0] = sym::core;
154+
let available_from_core = lookup_path(cx.tcx, namespace, &path_new).contains(&def_id);
155+
156+
path_new[0] = sym::alloc;
157+
let available_from_alloc = lookup_path(cx.tcx, namespace, &path_new).contains(&def_id);
158+
152159
self.lint_if_finish(
153160
cx,
154161
first_segment.ident.span,
155-
LintPoint::Available(last_segment.ident.span, lint, used_mod, replace_with),
162+
LintPoint {
163+
span: last_segment.ident.span,
164+
used_from: first_segment.ident.name,
165+
is_stable: is_stable(cx, def_id, self.msrv),
166+
available_from_core,
167+
available_from_alloc,
168+
},
156169
);
157170
}
158171
}
@@ -171,27 +184,78 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
171184
}
172185

173186
fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>) {
174-
let Some((krate_span, lint_points)) = lint_points else {
187+
let Some((krate_span, mut lint_points)) = lint_points else {
175188
return;
176189
};
177190

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+
// It's possible for multiple items to come from the same path.
192+
// For example, `std::vec` refers to a macro and a module.
193+
// In these cases, it's possible for them to have different availabilities.
194+
// `std::vec` as a macro and a module are both defined in `alloc` and unavailable in `core`.
195+
// Whereas, `std::env` is not available in `alloc`, and only the macro is available in `core`.
196+
// Since we aren't checking which of the shadowed items the user needs, we take the intersection
197+
// of these availabilities to ensure we don't provide the user a false positive.
198+
lint_points.sort_by_key(|lint_point| lint_point.span);
199+
lint_points.dedup_by(|a, b| {
200+
if a.span == b.span {
201+
b.is_stable &= a.is_stable;
202+
b.available_from_alloc &= a.available_from_alloc;
203+
b.available_from_core &= a.available_from_core;
204+
true
205+
} else {
206+
false
207+
}
208+
});
209+
210+
let mut core_span = MultiSpan::new();
211+
let mut alloc_span = MultiSpan::new();
212+
let mut all_from_std = true;
213+
let mut all_from_alloc = true;
214+
let mut all_core = true;
215+
let mut all_alloc = true;
216+
217+
for lint_point in lint_points {
218+
all_from_std &= lint_point.used_from == sym::std;
219+
all_from_alloc &= lint_point.used_from == sym::alloc;
220+
221+
if lint_point.is_stable && lint_point.available_from_core {
222+
core_span.push_primary_span(lint_point.span);
223+
} else {
224+
all_core = false;
225+
}
226+
227+
if lint_point.is_stable && lint_point.available_from_alloc {
228+
if !lint_point.available_from_core {
229+
alloc_span.push_primary_span(lint_point.span);
230+
}
231+
} else {
232+
all_alloc = false;
191233
}
192234
}
193235

194-
if !has_conflict && let Some((lint, used_mod, replace_with)) = lint {
236+
let mut helps = Vec::new();
237+
let mut suggestions = Vec::new();
238+
239+
if all_from_std {
240+
if all_core {
241+
suggestions.push((STD_INSTEAD_OF_CORE, &sym::std, &sym::core));
242+
helps.push((STD_INSTEAD_OF_ALLOC, &sym::std, &sym::alloc, alloc_span));
243+
} else if all_alloc {
244+
suggestions.push((STD_INSTEAD_OF_ALLOC, &sym::std, &sym::alloc));
245+
helps.push((STD_INSTEAD_OF_CORE, &sym::std, &sym::core, core_span));
246+
} else {
247+
helps.push((STD_INSTEAD_OF_CORE, &sym::std, &sym::core, core_span));
248+
helps.push((STD_INSTEAD_OF_ALLOC, &sym::std, &sym::alloc, alloc_span));
249+
}
250+
} else if all_from_alloc {
251+
if all_core {
252+
suggestions.push((ALLOC_INSTEAD_OF_CORE, &sym::alloc, &sym::core));
253+
} else {
254+
helps.push((ALLOC_INSTEAD_OF_CORE, &sym::alloc, &sym::core, core_span));
255+
}
256+
}
257+
258+
for (lint, used_mod, replace_with) in suggestions {
195259
span_lint_and_sugg(
196260
cx,
197261
lint,
@@ -201,21 +265,19 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
201265
(*replace_with).to_string(),
202266
Applicability::MachineApplicable,
203267
);
204-
return;
205268
}
206269

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-
);
270+
for (lint, used_mod, replace_with, span) in helps {
271+
for &span in span.primary_spans() {
272+
span_lint_and_help(
273+
cx,
274+
lint,
275+
span,
276+
format!("used import from `{used_mod}` instead of `{replace_with}`"),
277+
None,
278+
format!("consider importing the item from `{replace_with}`"),
279+
);
280+
}
219281
}
220282
}
221283

tests/ui/std_instead_of_core.fixed

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,23 @@ fn issue13158_msrv_1_80(_: &dyn std::error::Error) {}
116116
#[clippy::msrv = "1.81"]
117117
fn issue13158_msrv_1_81(_: &dyn core::error::Error) {}
118118
//~^ std_instead_of_core
119+
120+
#[warn(clippy::std_instead_of_alloc)]
121+
fn pr17252() {
122+
use core::result::{self, Iter, Result};
123+
//~^ std_instead_of_core
124+
125+
use alloc::str::{self as _};
126+
//~^ std_instead_of_alloc
127+
128+
use alloc::str::{self as _, FromStr as _};
129+
//~^ std_instead_of_alloc
130+
//~| std_instead_of_core
131+
132+
use alloc::fmt::{self, Write};
133+
//~^ std_instead_of_alloc
134+
//~| std_instead_of_core
135+
136+
// Macros aren't currently linted.
137+
use std::writeln;
138+
}

tests/ui/std_instead_of_core.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,23 @@ fn issue13158_msrv_1_80(_: &dyn std::error::Error) {}
116116
#[clippy::msrv = "1.81"]
117117
fn issue13158_msrv_1_81(_: &dyn std::error::Error) {}
118118
//~^ std_instead_of_core
119+
120+
#[warn(clippy::std_instead_of_alloc)]
121+
fn pr17252() {
122+
use std::result::{self, Iter, Result};
123+
//~^ std_instead_of_core
124+
125+
use std::str::{self as _};
126+
//~^ std_instead_of_alloc
127+
128+
use std::str::{self as _, FromStr as _};
129+
//~^ std_instead_of_alloc
130+
//~| std_instead_of_core
131+
132+
use std::fmt::{self, Write};
133+
//~^ std_instead_of_alloc
134+
//~| std_instead_of_core
135+
136+
// Macros aren't currently linted.
137+
use std::writeln;
138+
}

tests/ui/std_instead_of_core.stderr

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,45 @@ error: used import from `std` instead of `core`
109109
LL | fn issue13158_msrv_1_81(_: &dyn std::error::Error) {}
110110
| ^^^ help: consider importing the item from `core`: `core`
111111

112-
error: aborting due to 17 previous errors
112+
error: used import from `std` instead of `core`
113+
--> tests/ui/std_instead_of_core.rs:122:9
114+
|
115+
LL | use std::result::{self, Iter, Result};
116+
| ^^^ help: consider importing the item from `core`: `core`
117+
118+
error: used import from `std` instead of `alloc`
119+
--> tests/ui/std_instead_of_core.rs:125:9
120+
|
121+
LL | use std::str::{self as _};
122+
| ^^^ help: consider importing the item from `alloc`: `alloc`
123+
124+
error: used import from `std` instead of `alloc`
125+
--> tests/ui/std_instead_of_core.rs:128:9
126+
|
127+
LL | use std::str::{self as _, FromStr as _};
128+
| ^^^ help: consider importing the item from `alloc`: `alloc`
129+
130+
error: used import from `std` instead of `core`
131+
--> tests/ui/std_instead_of_core.rs:128:31
132+
|
133+
LL | use std::str::{self as _, FromStr as _};
134+
| ^^^^^^^
135+
|
136+
= help: consider importing the item from `core`
137+
138+
error: used import from `std` instead of `alloc`
139+
--> tests/ui/std_instead_of_core.rs:132:9
140+
|
141+
LL | use std::fmt::{self, Write};
142+
| ^^^ help: consider importing the item from `alloc`: `alloc`
143+
144+
error: used import from `std` instead of `core`
145+
--> tests/ui/std_instead_of_core.rs:132:26
146+
|
147+
LL | use std::fmt::{self, Write};
148+
| ^^^^^
149+
|
150+
= help: consider importing the item from `core`
151+
152+
error: aborting due to 23 previous errors
113153

tests/ui/std_instead_of_core_unfixable.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,26 @@ fn pr16964() {
2525
ffi::OsString,
2626
};
2727
}
28+
29+
#[warn(clippy::alloc_instead_of_core)]
30+
#[rustfmt::skip]
31+
fn pr17252() {
32+
extern crate alloc;
33+
34+
use alloc::str::{self as _, FromStr as _};
35+
//~^ alloc_instead_of_core
36+
37+
use std::sync::{
38+
Arc,
39+
Mutex,
40+
Weak,
41+
atomic::{
42+
AtomicPtr,
43+
Ordering,
44+
},
45+
};
46+
//~^^^^^^^^ std_instead_of_alloc
47+
//~^^^^^^^ std_instead_of_alloc
48+
//~^^^^^^ std_instead_of_core
49+
//~^^^^^^ std_instead_of_core
50+
}

tests/ui/std_instead_of_core_unfixable.stderr

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,47 @@ LL | collections::BTreeSet,
4242
|
4343
= help: consider importing the item from `alloc`
4444

45-
error: aborting due to 5 previous errors
45+
error: used import from `alloc` instead of `core`
46+
--> tests/ui/std_instead_of_core_unfixable.rs:34:33
47+
|
48+
LL | use alloc::str::{self as _, FromStr as _};
49+
| ^^^^^^^
50+
|
51+
= help: consider importing the item from `core`
52+
= note: `-D clippy::alloc-instead-of-core` implied by `-D warnings`
53+
= help: to override `-D warnings` add `#[allow(clippy::alloc_instead_of_core)]`
54+
55+
error: used import from `std` instead of `core`
56+
--> tests/ui/std_instead_of_core_unfixable.rs:42:13
57+
|
58+
LL | AtomicPtr,
59+
| ^^^^^^^^^
60+
|
61+
= help: consider importing the item from `core`
62+
63+
error: used import from `std` instead of `core`
64+
--> tests/ui/std_instead_of_core_unfixable.rs:43:13
65+
|
66+
LL | Ordering,
67+
| ^^^^^^^^
68+
|
69+
= help: consider importing the item from `core`
70+
71+
error: used import from `std` instead of `alloc`
72+
--> tests/ui/std_instead_of_core_unfixable.rs:38:9
73+
|
74+
LL | Arc,
75+
| ^^^
76+
|
77+
= help: consider importing the item from `alloc`
78+
79+
error: used import from `std` instead of `alloc`
80+
--> tests/ui/std_instead_of_core_unfixable.rs:40:9
81+
|
82+
LL | Weak,
83+
| ^^^^
84+
|
85+
= help: consider importing the item from `alloc`
86+
87+
error: aborting due to 10 previous errors
4688

0 commit comments

Comments
 (0)