Skip to content

Commit db1918a

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

6 files changed

Lines changed: 190 additions & 55 deletions

clippy_lints/src/std_instead_of_core.rs

Lines changed: 101 additions & 47 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;
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};
99
use rustc_lint::{LateContext, LateLintPass, Lint, 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};
13+
use std::collections::BTreeMap;
1314

1415
declare_clippy_lint! {
1516
/// ### What it does
@@ -93,7 +94,7 @@ impl_lint_pass!(StdReexports => [
9394
]);
9495

9596
pub struct StdReexports {
96-
lint_points: Option<(Span, Vec<LintPoint>)>,
97+
lint_points: Option<(Span, LintPoints)>,
9798
msrv: Msrv,
9899
}
99100

@@ -105,55 +106,105 @@ impl StdReexports {
105106
}
106107
}
107108

108-
fn lint_if_finish(&mut self, cx: &LateContext<'_>, krate: Span, lint_point: LintPoint) {
109-
match &mut self.lint_points {
109+
fn lint_if_finish(&mut self, cx: &LateContext<'_>, krate: Span, lint_point: LintPoint, path: &Path<'_>) {
110+
let mut lint_points = match &mut self.lint_points {
110111
Some((prev_krate, prev_lints)) if prev_krate.overlaps(krate) => {
111-
prev_lints.push(lint_point);
112+
prev_lints.lint_point.update(lint_point);
113+
prev_lints
112114
},
113-
_ => emit_lints(cx, self.lint_points.replace((krate, vec![lint_point]))),
115+
_ => {
116+
emit_lints(cx, self.lint_points.replace((krate, LintPoints::new(lint_point))));
117+
&mut self.lint_points.as_mut().unwrap().1
118+
},
119+
};
120+
121+
for segment in path
122+
.segments
123+
.iter()
124+
.skip_while(|segment| segment.ident.name == kw::PathRoot)
125+
{
126+
lint_points = lint_points
127+
.children
128+
.entry(segment.ident.span)
129+
.or_insert(LintPoints::new(lint_point));
130+
lint_points.lint_point.update(lint_point);
114131
}
115132
}
116133
}
117134

118-
#[derive(Debug)]
135+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
119136
enum LintPoint {
120-
Available(Span, &'static Lint, &'static str, &'static str),
137+
Available(Symbol, Symbol),
121138
Conflict,
122139
}
123140

141+
impl LintPoint {
142+
fn into_lint(self) -> Option<(&'static Lint, &'static Symbol, &'static Symbol)> {
143+
match self {
144+
Self::Available(sym::alloc, sym::core) => Some((ALLOC_INSTEAD_OF_CORE, &sym::alloc, &sym::core)),
145+
Self::Available(sym::std, sym::alloc) => Some((STD_INSTEAD_OF_ALLOC, &sym::std, &sym::alloc)),
146+
Self::Available(sym::std, sym::core) => Some((STD_INSTEAD_OF_CORE, &sym::std, &sym::core)),
147+
_ => None,
148+
}
149+
}
150+
151+
fn from_symbols(used: Symbol, defined_in: Symbol) -> Self {
152+
match (used, defined_in) {
153+
(sym::std, sym::core) => Self::Available(sym::std, sym::core),
154+
(sym::std, sym::alloc) => Self::Available(sym::std, sym::alloc),
155+
(sym::alloc, sym::core) => Self::Available(sym::alloc, sym::core),
156+
_ => Self::Conflict,
157+
}
158+
}
159+
160+
fn update(&mut self, other: Self) {
161+
*self = match (*self, other) {
162+
(Self::Conflict, _) | (_, Self::Conflict) => Self::Conflict,
163+
(Self::Available(a_used, a_replace), Self::Available(b_used, b_replace)) => {
164+
if a_used != b_used {
165+
Self::Conflict
166+
} else if a_replace == b_replace {
167+
other
168+
} else {
169+
Self::Available(a_used, sym::alloc)
170+
}
171+
},
172+
};
173+
}
174+
}
175+
176+
#[derive(Debug)]
177+
struct LintPoints {
178+
lint_point: LintPoint,
179+
children: BTreeMap<Span, LintPoints>,
180+
}
181+
182+
impl LintPoints {
183+
fn new(lint_point: LintPoint) -> Self {
184+
Self {
185+
lint_point,
186+
children: BTreeMap::new(),
187+
}
188+
}
189+
}
190+
124191
impl<'tcx> LateLintPass<'tcx> for StdReexports {
125192
fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
126193
if let Res::Def(def_kind, def_id) = path.res
127194
&& let Some(first_segment) = get_first_segment(path)
128-
&& is_stable(cx, def_id, self.msrv)
129195
&& !path.span.in_external_macro(cx.sess().source_map())
130196
&& !is_from_proc_macro(cx, &first_segment.ident)
131197
&& !matches!(def_kind, DefKind::Macro(_))
132-
&& let Some(last_segment) = path.segments.last()
133198
&& let Res::Def(DefKind::Mod, crate_def_id) = first_segment.res
134199
&& crate_def_id.is_crate_root()
135200
{
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-
},
201+
let lint_point = if is_stable(cx, def_id, self.msrv) {
202+
LintPoint::from_symbols(first_segment.ident.name, cx.tcx.crate_name(def_id.krate))
203+
} else {
204+
LintPoint::Conflict
150205
};
151206

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-
);
207+
self.lint_if_finish(cx, first_segment.ident.span, lint_point, path);
157208
}
158209
}
159210

@@ -170,28 +221,31 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
170221
}
171222
}
172223

173-
fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>) {
224+
fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, LintPoints)>) {
174225
let Some((krate_span, lint_points)) = lint_points else {
175226
return;
176227
};
177228

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-
},
229+
let mut combined = BTreeMap::new();
230+
let mut stack = vec![(krate_span, lint_points)];
231+
while let Some((span, lint_points)) = stack.pop() {
232+
if let LintPoint::Conflict = lint_points.lint_point {
233+
stack.extend(lint_points.children);
191234
}
235+
236+
combined
237+
.entry(lint_points.lint_point)
238+
.or_insert_with(MultiSpan::new)
239+
.push_primary_span(span);
192240
}
241+
let mut combined = combined.into_iter().collect::<Vec<_>>();
242+
combined.sort_by_key(|(_, span)| span.primary_span());
193243

194-
if !has_conflict && let Some((lint, used_mod, replace_with)) = lint {
244+
if let Some((lint, used_mod, replace_with)) = combined
245+
.first()
246+
.filter(|_| combined.len() == 1)
247+
.and_then(|first| first.0.into_lint())
248+
{
195249
span_lint_and_sugg(
196250
cx,
197251
lint,
@@ -204,8 +258,8 @@ fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>)
204258
return;
205259
}
206260

207-
for lint_point in lint_points {
208-
let LintPoint::Available(span, lint, used_mod, replace_with) = lint_point else {
261+
for (lint_point, span) in combined {
262+
let Some((lint, used_mod, replace_with)) = lint_point.into_lint() else {
209263
continue;
210264
};
211265
span_lint_and_help(

tests/ui/std_instead_of_core.fixed

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

9797
let layout = alloc::Layout::new::<u8>();
9898
}
99+
100+
#[warn(clippy::std_instead_of_alloc)]
101+
fn pr17252() {
102+
use alloc::fmt::{self, Write};
103+
//~^ std_instead_of_alloc
104+
105+
use core::result::{self, Iter, Result};
106+
//~^ std_instead_of_core
107+
}

tests/ui/std_instead_of_core.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,12 @@ 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::fmt::{self, Write};
103+
//~^ std_instead_of_alloc
104+
105+
use std::result::{self, Iter, Result};
106+
//~^ std_instead_of_core
107+
}

tests/ui/std_instead_of_core.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,17 @@ 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 `alloc`
101+
--> tests/ui/std_instead_of_core.rs:102:9
102+
|
103+
LL | use std::fmt::{self, Write};
104+
| ^^^ help: consider importing the item from `alloc`: `alloc`
105+
106+
error: used import from `std` instead of `core`
107+
--> tests/ui/std_instead_of_core.rs:105:9
108+
|
109+
LL | use std::result::{self, Iter, Result};
110+
| ^^^ help: consider importing the item from `core`: `core`
111+
112+
error: aborting due to 17 previous errors
101113

tests/ui/std_instead_of_core_unfixable.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,26 @@ fn issue15143() {
1313
use std::{error::Error, vec::Vec, fs::File};
1414
//~^ std_instead_of_core
1515
//~| std_instead_of_alloc
16+
17+
use std::sync::{
18+
Arc,
19+
Mutex,
20+
Weak,
21+
atomic::{
22+
AtomicPtr,
23+
Ordering,
24+
},
25+
};
26+
//~^^^^^^ std_instead_of_alloc
27+
//~^^^^^^ std_instead_of_core
28+
}
29+
30+
#[rustfmt::skip]
31+
fn pr16964() {
32+
use std::{
33+
borrow::Cow,
34+
collections::BTreeSet,
35+
ffi::OsString,
36+
};
37+
//~^^^ std_instead_of_alloc
1638
}
Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,59 @@
11
error: used import from `std` instead of `core`
2-
--> tests/ui/std_instead_of_core_unfixable.rs:7:43
2+
--> tests/ui/std_instead_of_core_unfixable.rs:7:37
33
|
44
LL | use std::{collections::HashMap, hash::Hash};
5-
| ^^^^
5+
| ^^^^
66
|
77
= help: consider importing the item from `core`
88
= note: `-D clippy::std-instead-of-core` implied by `-D warnings`
99
= help: to override `-D warnings` add `#[allow(clippy::std_instead_of_core)]`
1010

1111
error: used import from `std` instead of `core`
12-
--> tests/ui/std_instead_of_core_unfixable.rs:13:22
12+
--> tests/ui/std_instead_of_core_unfixable.rs:13:15
1313
|
1414
LL | use std::{error::Error, vec::Vec, fs::File};
15-
| ^^^^^
15+
| ^^^^^
1616
|
1717
= help: consider importing the item from `core`
1818

1919
error: used import from `std` instead of `alloc`
20-
--> tests/ui/std_instead_of_core_unfixable.rs:13:34
20+
--> tests/ui/std_instead_of_core_unfixable.rs:13:29
2121
|
2222
LL | use std::{error::Error, vec::Vec, fs::File};
23-
| ^^^
23+
| ^^^
2424
|
2525
= help: consider importing the item from `alloc`
2626
= note: `-D clippy::std-instead-of-alloc` implied by `-D warnings`
2727
= help: to override `-D warnings` add `#[allow(clippy::std_instead_of_alloc)]`
2828

29-
error: aborting due to 3 previous errors
29+
error: used import from `std` instead of `alloc`
30+
--> tests/ui/std_instead_of_core_unfixable.rs:18:9
31+
|
32+
LL | Arc,
33+
| ^^^
34+
LL | Mutex,
35+
LL | Weak,
36+
| ^^^^
37+
|
38+
= help: consider importing the item from `alloc`
39+
40+
error: used import from `std` instead of `core`
41+
--> tests/ui/std_instead_of_core_unfixable.rs:21:9
42+
|
43+
LL | atomic::{
44+
| ^^^^^^
45+
|
46+
= help: consider importing the item from `core`
47+
48+
error: used import from `std` instead of `alloc`
49+
--> tests/ui/std_instead_of_core_unfixable.rs:33:9
50+
|
51+
LL | borrow::Cow,
52+
| ^^^^^^
53+
LL | collections::BTreeSet,
54+
| ^^^^^^^^^^^
55+
|
56+
= help: consider importing the item from `alloc`
57+
58+
error: aborting due to 6 previous errors
3059

0 commit comments

Comments
 (0)