Skip to content

Commit 3a0a721

Browse files
committed
Add new lint: std_wildcard_imports
1 parent a6e40fa commit 3a0a721

19 files changed

+305
-59
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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::{import_span_and_sugg, is_prelude_import};
3+
use rustc_hir::{Item, ItemKind, PathSegment, UseKind};
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_session::declare_lint_pass;
6+
use rustc_span::sym;
7+
use rustc_span::symbol::{STDLIB_STABLE_CRATES, kw};
8+
9+
declare_clippy_lint! {
10+
/// ### What it does
11+
/// Checks for wildcard imports `use _::*` from the standard library crates.
12+
///
13+
/// ### Why is this bad?
14+
/// Wildcard imports can pollute the namespace. This is especially bad when importing from the
15+
/// standard library through wildcards:
16+
///
17+
/// ```no_run
18+
/// use foo::bar; // Imports a function named bar
19+
/// use std::rc::*; // Does not have a function named bar initially
20+
///
21+
/// # mod foo { pub fn bar() {} }
22+
/// bar();
23+
/// ```
24+
///
25+
/// When the `std::rc` module later adds a function named `bar`, the compiler cannot decide
26+
/// which function to call, causing a compilation error.
27+
///
28+
/// ### Exceptions
29+
/// Wildcard imports are allowed from modules whose names contain `prelude`. Many crates
30+
/// (including the standard library) provide modules named "prelude" specifically designed
31+
/// for wildcard import.
32+
///
33+
/// ### Example
34+
/// ```no_run
35+
/// use std::rc::*;
36+
///
37+
/// let _ = Rc::new(5);
38+
/// ```
39+
///
40+
/// Use instead:
41+
/// ```no_run
42+
/// use std::rc::Rc;
43+
///
44+
/// let _ = Rc::new(5);
45+
/// ```
46+
#[clippy::version = "1.89.0"]
47+
pub STD_WILDCARD_IMPORTS,
48+
style,
49+
"lint `use _::*` from the standard library crates"
50+
}
51+
52+
declare_lint_pass!(StdWildcardImports => [STD_WILDCARD_IMPORTS]);
53+
54+
impl LateLintPass<'_> for StdWildcardImports {
55+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
56+
if let ItemKind::Use(use_path, UseKind::Glob) = item.kind
57+
&& !is_prelude_import(use_path.segments)
58+
&& is_std_import(use_path.segments)
59+
&& let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id)
60+
&& !used_imports.is_empty() // Already handled by `unused_imports`
61+
&& !used_imports.contains(&kw::Underscore)
62+
{
63+
let (span, sugg, applicability) = import_span_and_sugg(cx, use_path, item);
64+
65+
span_lint_and_sugg(
66+
cx,
67+
STD_WILDCARD_IMPORTS,
68+
span,
69+
"usage of wildcard import from `std` crates",
70+
"try",
71+
sugg,
72+
applicability,
73+
);
74+
}
75+
}
76+
}
77+
78+
// Checks for the standard libraries, including `test` crate.
79+
fn is_std_import(segments: &[PathSegment<'_>]) -> bool {
80+
let Some(first_segment_name) = segments.first().map(|ps| ps.ident.name) else {
81+
return false;
82+
};
83+
84+
STDLIB_STABLE_CRATES.contains(&first_segment_name) || first_segment_name == sym::test
85+
}

clippy_lints/src/wildcard_imports.rs

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
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::{import_span_and_sugg, is_in_test, is_prelude_import};
54
use rustc_data_structures::fx::FxHashSet;
6-
use rustc_errors::Applicability;
75
use rustc_hir::def::{DefKind, Res};
86
use rustc_hir::{Item, ItemKind, PathSegment, UseKind};
97
use rustc_lint::{LateContext, LateLintPass, LintContext};
108
use rustc_middle::ty;
119
use rustc_session::impl_lint_pass;
1210
use rustc_span::symbol::kw;
13-
use rustc_span::{BytePos, sym};
1411

1512
declare_clippy_lint! {
1613
/// ### What it does
@@ -134,40 +131,7 @@ impl LateLintPass<'_> for WildcardImports {
134131
&& !used_imports.is_empty() // Already handled by `unused_imports`
135132
&& !used_imports.contains(&kw::Underscore)
136133
{
137-
let mut applicability = Applicability::MachineApplicable;
138-
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-
};
165-
166-
let sugg = if braced_glob {
167-
imports_string
168-
} else {
169-
format!("{import_source_snippet}::{imports_string}")
170-
};
134+
let (span, sugg, applicability) = import_span_and_sugg(cx, use_path, item);
171135

172136
// Glob imports always have a single resolution.
173137
let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res[0] {
@@ -184,20 +148,12 @@ impl LateLintPass<'_> for WildcardImports {
184148
impl WildcardImports {
185149
fn check_exceptions(&self, cx: &LateContext<'_>, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
186150
item.span.from_expansion()
187-
|| is_prelude_import(segments)
151+
|| is_prelude_import(segments) // Many crates have a prelude, and it is imported as a glob by design.
188152
|| is_allowed_via_config(segments, &self.allowed_segments)
189153
|| (is_super_only_import(segments) && is_in_test(cx.tcx, item.hir_id()))
190154
}
191155
}
192156

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-
201157
// Allow "super::*" imports in tests.
202158
fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
203159
segments.len() == 1 && segments[0].ident.name == kw::Super

clippy_utils/src/lib.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ use rustc_attr_data_structures::{AttributeKind, find_attr};
9393
use rustc_data_structures::fx::FxHashMap;
9494
use rustc_data_structures::packed::Pu128;
9595
use rustc_data_structures::unhash::UnindexMap;
96+
use rustc_errors::Applicability;
9697
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
9798
use rustc_hir::def::{DefKind, Res};
9899
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
@@ -104,7 +105,7 @@ use rustc_hir::{
104105
CoroutineKind, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl,
105106
ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode,
106107
Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem,
107-
TraitItemKind, TraitRef, TyKind, UnOp, def,
108+
TraitItemKind, TraitRef, TyKind, UnOp, UsePath, def,
108109
};
109110
use rustc_lexer::{TokenKind, tokenize};
110111
use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -121,12 +122,13 @@ use rustc_middle::ty::{
121122
use rustc_span::hygiene::{ExpnKind, MacroKind};
122123
use rustc_span::source_map::SourceMap;
123124
use rustc_span::symbol::{Ident, Symbol, kw};
124-
use rustc_span::{InnerSpan, Span};
125+
use rustc_span::{BytePos, InnerSpan, Span};
125126
use source::walk_span_to_context;
126127
use visitors::{Visitable, for_each_unconsumed_temporary};
127128

128129
use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
129130
use crate::higher::Range;
131+
use crate::source::{snippet, snippet_with_applicability};
130132
use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
131133
use crate::visitors::for_each_expr_without_closures;
132134

@@ -3471,3 +3473,55 @@ pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
34713473
None
34723474
}
34733475
}
3476+
3477+
/// Returns true for `...prelude::...` imports.
3478+
pub fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
3479+
segments
3480+
.iter()
3481+
.any(|ps| ps.ident.as_str().contains(sym::prelude.as_str()))
3482+
}
3483+
3484+
pub fn import_span_and_sugg(
3485+
cx: &LateContext<'_>,
3486+
use_path: &UsePath<'_>,
3487+
item: &Item<'_>,
3488+
) -> (Span, String, Applicability) {
3489+
let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id);
3490+
3491+
let mut applicability = Applicability::MachineApplicable;
3492+
let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
3493+
let (span, braced_glob) = if import_source_snippet.is_empty() {
3494+
// This is a `_::{_, *}` import
3495+
// In this case `use_path.span` is empty and ends directly in front of the `*`,
3496+
// so we need to extend it by one byte.
3497+
(use_path.span.with_hi(use_path.span.hi() + BytePos(1)), true)
3498+
} else {
3499+
// In this case, the `use_path.span` ends right before the `::*`, so we need to
3500+
// extend it up to the `*`. Since it is hard to find the `*` in weird
3501+
// formatting like `use _ :: *;`, we extend it up to, but not including the
3502+
// `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
3503+
// can just use the end of the item span
3504+
let mut span = use_path.span.with_hi(item.span.hi());
3505+
if snippet(cx, span, "").ends_with(';') {
3506+
span = use_path.span.with_hi(item.span.hi() - BytePos(1));
3507+
}
3508+
(span, false)
3509+
};
3510+
3511+
let mut imports: Vec<_> = used_imports.iter().map(ToString::to_string).collect();
3512+
let imports_string = if imports.len() == 1 {
3513+
imports.pop().unwrap()
3514+
} else if braced_glob {
3515+
imports.join(", ")
3516+
} else {
3517+
format!("{{{}}}", imports.join(", "))
3518+
};
3519+
3520+
let sugg = if braced_glob {
3521+
imports_string
3522+
} else {
3523+
format!("{import_source_snippet}::{imports_string}")
3524+
};
3525+
3526+
(span, sugg, applicability)
3527+
}

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)