Skip to content

Commit de87094

Browse files
committed
Add new lint: std_wildcard_imports
1 parent 76118ec commit de87094

16 files changed

+268
-48
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6296,6 +6296,7 @@ Released 2018-09-13
62966296
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
62976297
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
62986298
[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
6299+
[`std_wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_wildcard_imports
62996300
[`str_split_at_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_split_at_newline
63006301
[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
63016302
[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
779779
crate::visibility::PUB_WITHOUT_SHORTHAND_INFO,
780780
crate::visibility::PUB_WITH_SHORTHAND_INFO,
781781
crate::wildcard_imports::ENUM_GLOB_USE_INFO,
782+
crate::wildcard_imports::STD_WILDCARD_IMPORTS_INFO,
782783
crate::wildcard_imports::WILDCARD_IMPORTS_INFO,
783784
crate::write::PRINTLN_EMPTY_STRING_INFO,
784785
crate::write::PRINT_LITERAL_INFO,

clippy_lints/src/wildcard_imports.rs

Lines changed: 109 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ use clippy_config::Conf;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::is_in_test;
44
use clippy_utils::source::{snippet, snippet_with_applicability};
5-
use rustc_data_structures::fx::FxHashSet;
5+
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
66
use rustc_errors::Applicability;
77
use rustc_hir::def::{DefKind, Res};
88
use rustc_hir::{Item, ItemKind, PathSegment, UseKind};
99
use rustc_lint::{LateContext, LateLintPass, LintContext};
1010
use rustc_middle::ty;
1111
use rustc_session::impl_lint_pass;
12-
use rustc_span::BytePos;
13-
use rustc_span::symbol::kw;
12+
use rustc_span::symbol::{STDLIB_STABLE_CRATES, Symbol, kw};
13+
use rustc_span::{BytePos, Span, sym};
1414

1515
declare_clippy_lint! {
1616
/// ### What it does
@@ -100,6 +100,43 @@ declare_clippy_lint! {
100100
"lint `use _::*` statements"
101101
}
102102

103+
declare_clippy_lint! {
104+
/// ### What it does
105+
/// Checks for wildcard imports `use _::*` from the standard library crates.
106+
///
107+
/// ### Why is this bad?
108+
/// Wildcard imports from the standard library crates can lead to breakages due to name
109+
/// resolution ambiguities when the standard library introduces new items with the same names
110+
/// as locally defined items.
111+
///
112+
/// ### Exceptions
113+
/// Wildcard imports are allowed from modules whose names contain `prelude`. Many crates
114+
/// (including the standard library) provide modules named "prelude" specifically designed
115+
/// for wildcard imports.
116+
///
117+
/// ### Example
118+
/// ```no_run
119+
/// use foo::bar;
120+
/// use std::rc::*;
121+
///
122+
/// # mod foo { pub fn bar() { let _ = Rc::new(5); } }
123+
/// bar();
124+
/// ```
125+
///
126+
/// Use instead:
127+
/// ```no_run
128+
/// use foo::bar;
129+
/// use std::rc::Rc;
130+
///
131+
/// # mod foo { pub fn bar() { let _ = Rc::new(5); } }
132+
/// bar();
133+
/// ```
134+
#[clippy::version = "1.89.0"]
135+
pub STD_WILDCARD_IMPORTS,
136+
pedantic,
137+
"lint `use _::*` from the standard library crates"
138+
}
139+
103140
pub struct WildcardImports {
104141
warn_on_all: bool,
105142
allowed_segments: FxHashSet<String>,
@@ -114,7 +151,7 @@ impl WildcardImports {
114151
}
115152
}
116153

117-
impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]);
154+
impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS, STD_WILDCARD_IMPORTS]);
118155

119156
impl LateLintPass<'_> for WildcardImports {
120157
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
@@ -129,51 +166,33 @@ impl LateLintPass<'_> for WildcardImports {
129166
return;
130167
}
131168
if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind
132-
&& (self.warn_on_all || !self.check_exceptions(cx, item, use_path.segments))
169+
&& (self.warn_on_all
170+
|| !self.check_exceptions(cx, item, use_path.segments)
171+
|| (!is_prelude_import(use_path.segments) && is_std_import(use_path.segments)))
133172
&& let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id)
134173
&& !used_imports.is_empty() // Already handled by `unused_imports`
135174
&& !used_imports.contains(&kw::Underscore)
136175
{
137176
let mut applicability = Applicability::MachineApplicable;
138177
let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
139-
let (span, braced_glob) = if import_source_snippet.is_empty() {
140-
// This is a `_::{_, *}` import
141-
// In this case `use_path.span` is empty and ends directly in front of the `*`,
142-
// so we need to extend it by one byte.
143-
(use_path.span.with_hi(use_path.span.hi() + BytePos(1)), true)
144-
} else {
145-
// In this case, the `use_path.span` ends right before the `::*`, so we need to
146-
// extend it up to the `*`. Since it is hard to find the `*` in weird
147-
// formatting like `use _ :: *;`, we extend it up to, but not including the
148-
// `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
149-
// can just use the end of the item span
150-
let mut span = use_path.span.with_hi(item.span.hi());
151-
if snippet(cx, span, "").ends_with(';') {
152-
span = use_path.span.with_hi(item.span.hi() - BytePos(1));
153-
}
154-
(span, false)
155-
};
156-
157-
let mut imports: Vec<_> = used_imports.iter().map(ToString::to_string).collect();
158-
let imports_string = if imports.len() == 1 {
159-
imports.pop().unwrap()
160-
} else if braced_glob {
161-
imports.join(", ")
162-
} else {
163-
format!("{{{}}}", imports.join(", "))
164-
};
165178

166-
let sugg = if braced_glob {
167-
imports_string
168-
} else {
169-
format!("{import_source_snippet}::{imports_string}")
170-
};
179+
let span = whole_glob_import_span(cx, item, import_source_snippet.is_empty())
180+
.expect("Not a glob import statement");
181+
let sugg = sugg_glob_import(&import_source_snippet, used_imports);
171182

172183
// Glob imports always have a single resolution. Enums are in the value namespace.
173184
let (lint, message) = if let Some(Res::Def(DefKind::Enum, _)) = use_path.res.value_ns {
174-
(ENUM_GLOB_USE, "usage of wildcard import for enum variants")
185+
(
186+
ENUM_GLOB_USE,
187+
String::from("usage of wildcard import for enum variants"),
188+
)
189+
} else if is_std_import(use_path.segments) {
190+
(
191+
STD_WILDCARD_IMPORTS,
192+
format!("usage of wildcard import from `{}`", use_path.segments[0].ident),
193+
)
175194
} else {
176-
(WILDCARD_IMPORTS, "usage of wildcard import")
195+
(WILDCARD_IMPORTS, String::from("usage of wildcard import"))
177196
};
178197

179198
span_lint_and_sugg(cx, lint, span, message, "try", sugg, applicability);
@@ -201,10 +220,62 @@ fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
201220
segments.len() == 1 && segments[0].ident.name == kw::Super
202221
}
203222

223+
// Checks for the standard libraries, including `test` crate.
224+
fn is_std_import(segments: &[PathSegment<'_>]) -> bool {
225+
let Some(first_segment_name) = segments.first().map(|ps| ps.ident.name) else {
226+
return false;
227+
};
228+
229+
STDLIB_STABLE_CRATES.contains(&first_segment_name) || first_segment_name == sym::test
230+
}
231+
204232
// Allow skipping imports containing user configured segments,
205233
// i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml`
206234
fn is_allowed_via_config(segments: &[PathSegment<'_>], allowed_segments: &FxHashSet<String>) -> bool {
207235
// segment matching need to be exact instead of using 'contains', in case user unintentionally put
208236
// a single character in the config thus skipping most of the warnings.
209237
segments.iter().any(|seg| allowed_segments.contains(seg.ident.as_str()))
210238
}
239+
240+
// Returns the entire span for a given glob import statement, including the `*` symbol.
241+
fn whole_glob_import_span(cx: &LateContext<'_>, item: &Item<'_>, braced_glob: bool) -> Option<Span> {
242+
let ItemKind::Use(use_path, UseKind::Glob) = item.kind else {
243+
return None;
244+
};
245+
246+
if braced_glob {
247+
// This is a `_::{_, *}` import
248+
// In this case `use_path.span` is empty and ends directly in front of the `*`,
249+
// so we need to extend it by one byte.
250+
Some(use_path.span.with_hi(use_path.span.hi() + BytePos(1)))
251+
} else {
252+
// In this case, the `use_path.span` ends right before the `::*`, so we need to
253+
// extend it up to the `*`. Since it is hard to find the `*` in weird
254+
// formatting like `use _ :: *;`, we extend it up to, but not including the
255+
// `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
256+
// can just use the end of the item span
257+
let mut span = use_path.span.with_hi(item.span.hi());
258+
if snippet(cx, span, "").ends_with(';') {
259+
span = use_path.span.with_hi(item.span.hi() - BytePos(1));
260+
}
261+
Some(span)
262+
}
263+
}
264+
265+
// Generates a suggestion for a glob import using only the actually used items.
266+
fn sugg_glob_import(import_source_snippet: &str, used_imports: &FxIndexSet<Symbol>) -> String {
267+
let mut imports: Vec<_> = used_imports.iter().map(ToString::to_string).collect();
268+
let imports_string = if imports.len() == 1 {
269+
imports.pop().unwrap()
270+
} else if import_source_snippet.is_empty() {
271+
imports.join(", ")
272+
} else {
273+
format!("{{{}}}", imports.join(", "))
274+
};
275+
276+
if import_source_snippet.is_empty() {
277+
imports_string
278+
} else {
279+
format!("{import_source_snippet}::{imports_string}")
280+
}
281+
}

tests/ui/crashes/ice-11422.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![warn(clippy::implied_bounds_in_impls)]
22

33
use std::fmt::Debug;
4-
use std::ops::*;
4+
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
55

66
fn r#gen() -> impl PartialOrd + Debug {}
77
//~^ implied_bounds_in_impls

tests/ui/crashes/ice-11422.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![warn(clippy::implied_bounds_in_impls)]
22

33
use std::fmt::Debug;
4-
use std::ops::*;
4+
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
55

66
fn r#gen() -> impl PartialOrd + PartialEq + Debug {}
77
//~^ implied_bounds_in_impls

tests/ui/enum_glob_use.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::enum_glob_use)]
2-
#![allow(unused)]
2+
#![allow(unused, clippy::std_wildcard_imports)]
33
#![warn(unused_imports)]
44

55
use std::cmp::Ordering::Less;

tests/ui/enum_glob_use.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::enum_glob_use)]
2-
#![allow(unused)]
2+
#![allow(unused, clippy::std_wildcard_imports)]
33
#![warn(unused_imports)]
44

55
use std::cmp::Ordering::*;

tests/ui/explicit_iter_loop.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
)]
1111

1212
use core::slice;
13-
use std::collections::*;
13+
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
1414

1515
fn main() {
1616
let mut vec = vec![1, 2, 3, 4];

tests/ui/explicit_iter_loop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
)]
1111

1212
use core::slice;
13-
use std::collections::*;
13+
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
1414

1515
fn main() {
1616
let mut vec = vec![1, 2, 3, 4];

tests/ui/for_kv_map.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![warn(clippy::for_kv_map)]
22
#![allow(clippy::used_underscore_binding)]
33

4-
use std::collections::*;
4+
use std::collections::HashMap;
55
use std::rc::Rc;
66

77
fn main() {

0 commit comments

Comments
 (0)