Skip to content

Commit 5f8b8e3

Browse files
committed
Add new lint: std_wildcard_imports
1 parent 3927a61 commit 5f8b8e3

19 files changed

+313
-56
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6289,6 +6289,7 @@ Released 2018-09-13
62896289
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
62906290
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
62916291
[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
6292+
[`std_wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_wildcard_imports
62926293
[`str_split_at_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_split_at_newline
62936294
[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
62946295
[`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
@@ -676,6 +676,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
676676
crate::std_instead_of_core::ALLOC_INSTEAD_OF_CORE_INFO,
677677
crate::std_instead_of_core::STD_INSTEAD_OF_ALLOC_INFO,
678678
crate::std_instead_of_core::STD_INSTEAD_OF_CORE_INFO,
679+
crate::std_wildcard_imports::STD_WILDCARD_IMPORTS_INFO,
679680
crate::string_patterns::MANUAL_PATTERN_CHAR_COMPARISON_INFO,
680681
crate::string_patterns::SINGLE_CHAR_PATTERN_INFO,
681682
crate::strings::STRING_ADD_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ mod size_of_in_element_count;
345345
mod size_of_ref;
346346
mod slow_vector_initialization;
347347
mod std_instead_of_core;
348+
mod std_wildcard_imports;
348349
mod string_patterns;
349350
mod strings;
350351
mod strlen_on_c_strings;
@@ -946,5 +947,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
946947
store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap));
947948
store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix));
948949
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
950+
store.register_late_pass(|_| Box::new(std_wildcard_imports::StdWildcardImports));
949951
// add lints here, do not remove this comment, it's used in `new_lint`
950952
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::{is_prelude_import, sugg_glob_import, whole_glob_import_span};
4+
use rustc_errors::Applicability;
5+
use rustc_hir::{Item, ItemKind, PathSegment, UseKind};
6+
use rustc_lint::{LateContext, LateLintPass};
7+
use rustc_session::declare_lint_pass;
8+
use rustc_span::sym;
9+
use rustc_span::symbol::{STDLIB_STABLE_CRATES, kw};
10+
11+
declare_clippy_lint! {
12+
/// ### What it does
13+
/// Checks for wildcard imports `use _::*` from the standard library crates.
14+
///
15+
/// ### Why is this bad?
16+
/// Wildcard imports can pollute the namespace. This is especially bad when importing from the
17+
/// standard library through wildcards:
18+
///
19+
/// ```no_run
20+
/// use foo::bar; // Imports a function named bar
21+
/// use std::rc::*; // Does not have a function named bar initially
22+
///
23+
/// # mod foo { pub fn bar() {} }
24+
/// bar();
25+
/// ```
26+
///
27+
/// When the `std::rc` module later adds a function named `bar`, the compiler cannot decide
28+
/// which function to call, causing a compilation error.
29+
///
30+
/// ### Exceptions
31+
/// Wildcard imports are allowed from modules whose names contain `prelude`. Many crates
32+
/// (including the standard library) provide modules named "prelude" specifically designed
33+
/// for wildcard import.
34+
///
35+
/// ### Example
36+
/// ```no_run
37+
/// use std::rc::*;
38+
///
39+
/// let _ = Rc::new(5);
40+
/// ```
41+
///
42+
/// Use instead:
43+
/// ```no_run
44+
/// use std::rc::Rc;
45+
///
46+
/// let _ = Rc::new(5);
47+
/// ```
48+
#[clippy::version = "1.89.0"]
49+
pub STD_WILDCARD_IMPORTS,
50+
style,
51+
"lint `use _::*` from the standard library crates"
52+
}
53+
54+
declare_lint_pass!(StdWildcardImports => [STD_WILDCARD_IMPORTS]);
55+
56+
impl LateLintPass<'_> for StdWildcardImports {
57+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
58+
if let ItemKind::Use(use_path, UseKind::Glob) = item.kind
59+
&& !is_prelude_import(use_path.segments)
60+
&& is_std_import(use_path.segments)
61+
&& let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id)
62+
&& !used_imports.is_empty() // Already handled by `unused_imports`
63+
&& !used_imports.contains(&kw::Underscore)
64+
{
65+
let mut applicability = Applicability::MachineApplicable;
66+
let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
67+
68+
let span = whole_glob_import_span(cx, item, import_source_snippet.is_empty())
69+
.expect("Not a glob import statement");
70+
let sugg = sugg_glob_import(&import_source_snippet, used_imports);
71+
72+
span_lint_and_sugg(
73+
cx,
74+
STD_WILDCARD_IMPORTS,
75+
span,
76+
"usage of wildcard import from `std` crates",
77+
"try",
78+
sugg,
79+
applicability,
80+
);
81+
}
82+
}
83+
}
84+
85+
// Checks for the standard libraries, including `test` crate.
86+
fn is_std_import(segments: &[PathSegment<'_>]) -> bool {
87+
let Some(first_segment_name) = segments.first().map(|ps| ps.ident.name) else {
88+
return false;
89+
};
90+
91+
STDLIB_STABLE_CRATES.contains(&first_segment_name) || first_segment_name == sym::test
92+
}

clippy_lints/src/wildcard_imports.rs

Lines changed: 6 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
3-
use clippy_utils::is_in_test;
4-
use clippy_utils::source::{snippet, snippet_with_applicability};
3+
use clippy_utils::source::snippet_with_applicability;
4+
use clippy_utils::{is_in_test, is_prelude_import, sugg_glob_import, whole_glob_import_span};
55
use rustc_data_structures::fx::FxHashSet;
66
use rustc_errors::Applicability;
77
use rustc_hir::def::{DefKind, Res};
@@ -10,7 +10,6 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
1010
use rustc_middle::ty;
1111
use rustc_session::impl_lint_pass;
1212
use rustc_span::symbol::kw;
13-
use rustc_span::{BytePos, sym};
1413

1514
declare_clippy_lint! {
1615
/// ### What it does
@@ -136,38 +135,10 @@ impl LateLintPass<'_> for WildcardImports {
136135
{
137136
let mut applicability = Applicability::MachineApplicable;
138137
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-
};
156138

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-
};
165-
166-
let sugg = if braced_glob {
167-
imports_string
168-
} else {
169-
format!("{import_source_snippet}::{imports_string}")
170-
};
139+
let span = whole_glob_import_span(cx, item, import_source_snippet.is_empty())
140+
.expect("Not a glob import statement");
141+
let sugg = sugg_glob_import(&import_source_snippet, used_imports);
171142

172143
// Glob imports always have a single resolution.
173144
let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res[0] {
@@ -184,20 +155,12 @@ impl LateLintPass<'_> for WildcardImports {
184155
impl WildcardImports {
185156
fn check_exceptions(&self, cx: &LateContext<'_>, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
186157
item.span.from_expansion()
187-
|| is_prelude_import(segments)
158+
|| is_prelude_import(segments) // Many crates have a prelude, and it is imported as a glob by design.
188159
|| is_allowed_via_config(segments, &self.allowed_segments)
189160
|| (is_super_only_import(segments) && is_in_test(cx.tcx, item.hir_id()))
190161
}
191162
}
192163

193-
// Allow "...prelude::..::*" imports.
194-
// Many crates have a prelude, and it is imported as a glob by design.
195-
fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
196-
segments
197-
.iter()
198-
.any(|ps| ps.ident.as_str().contains(sym::prelude.as_str()))
199-
}
200-
201164
// Allow "super::*" imports in tests.
202165
fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
203166
segments.len() == 1 && segments[0].ident.name == kw::Super

clippy_utils/src/lib.rs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ use itertools::Itertools;
9090
use rustc_abi::Integer;
9191
use rustc_ast::ast::{self, LitKind, RangeLimits};
9292
use rustc_attr_data_structures::{AttributeKind, find_attr};
93-
use rustc_data_structures::fx::FxHashMap;
93+
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
9494
use rustc_data_structures::packed::Pu128;
9595
use rustc_data_structures::unhash::UnindexMap;
9696
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
@@ -104,7 +104,7 @@ use rustc_hir::{
104104
CoroutineKind, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl,
105105
ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode,
106106
Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem,
107-
TraitItemKind, TraitRef, TyKind, UnOp, def,
107+
TraitItemKind, TraitRef, TyKind, UnOp, UseKind, def,
108108
};
109109
use rustc_lexer::{TokenKind, tokenize};
110110
use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -121,12 +121,13 @@ use rustc_middle::ty::{
121121
use rustc_span::hygiene::{ExpnKind, MacroKind};
122122
use rustc_span::source_map::SourceMap;
123123
use rustc_span::symbol::{Ident, Symbol, kw};
124-
use rustc_span::{InnerSpan, Span};
124+
use rustc_span::{BytePos, InnerSpan, Span};
125125
use source::walk_span_to_context;
126126
use visitors::{Visitable, for_each_unconsumed_temporary};
127127

128128
use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
129129
use crate::higher::Range;
130+
use crate::source::snippet;
130131
use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
131132
use crate::visitors::for_each_expr_without_closures;
132133

@@ -3473,3 +3474,53 @@ pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
34733474
None
34743475
}
34753476
}
3477+
3478+
/// Returns true for `...prelude::...` imports.
3479+
pub fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
3480+
segments
3481+
.iter()
3482+
.any(|ps| ps.ident.as_str().contains(sym::prelude.as_str()))
3483+
}
3484+
3485+
/// Returns the entire span for a given glob import statement, including the `*` symbol.
3486+
pub fn whole_glob_import_span(cx: &LateContext<'_>, item: &Item<'_>, braced_glob: bool) -> Option<Span> {
3487+
let ItemKind::Use(use_path, UseKind::Glob) = item.kind else {
3488+
return None;
3489+
};
3490+
3491+
if braced_glob {
3492+
// This is a `_::{_, *}` import
3493+
// In this case `use_path.span` is empty and ends directly in front of the `*`,
3494+
// so we need to extend it by one byte.
3495+
Some(use_path.span.with_hi(use_path.span.hi() + BytePos(1)))
3496+
} else {
3497+
// In this case, the `use_path.span` ends right before the `::*`, so we need to
3498+
// extend it up to the `*`. Since it is hard to find the `*` in weird
3499+
// formatting like `use _ :: *;`, we extend it up to, but not including the
3500+
// `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
3501+
// can just use the end of the item span
3502+
let mut span = use_path.span.with_hi(item.span.hi());
3503+
if snippet(cx, span, "").ends_with(';') {
3504+
span = use_path.span.with_hi(item.span.hi() - BytePos(1));
3505+
}
3506+
Some(span)
3507+
}
3508+
}
3509+
3510+
/// Generates a suggestion for a glob import using only the actually used items.
3511+
pub fn sugg_glob_import(import_source_snippet: &str, used_imports: &FxIndexSet<Symbol>) -> String {
3512+
let mut imports: Vec<_> = used_imports.iter().map(ToString::to_string).collect();
3513+
let imports_string = if imports.len() == 1 {
3514+
imports.pop().unwrap()
3515+
} else if import_source_snippet.is_empty() {
3516+
imports.join(", ")
3517+
} else {
3518+
format!("{{{}}}", imports.join(", "))
3519+
};
3520+
3521+
if import_source_snippet.is_empty() {
3522+
imports_string
3523+
} else {
3524+
format!("{import_source_snippet}::{imports_string}")
3525+
}
3526+
}

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() {

tests/ui/for_kv_map.rs

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() {

tests/ui/into_iter_on_ref.fixed

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

44
struct X;
5-
use std::collections::*;
5+
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
66

77
fn main() {
88
for _ in &[1, 2, 3] {}

tests/ui/into_iter_on_ref.rs

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

44
struct X;
5-
use std::collections::*;
5+
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
66

77
fn main() {
88
for _ in &[1, 2, 3] {}

0 commit comments

Comments
 (0)