Skip to content

Commit 80ac147

Browse files
committed
Add new lint: std_wildcard_imports
1 parent d7b27ec commit 80ac147

19 files changed

+313
-56
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6291,6 +6291,7 @@ Released 2018-09-13
62916291
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
62926292
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
62936293
[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
6294+
[`std_wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_wildcard_imports
62946295
[`str_split_at_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_split_at_newline
62956296
[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
62966297
[`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
@@ -678,6 +678,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
678678
crate::std_instead_of_core::ALLOC_INSTEAD_OF_CORE_INFO,
679679
crate::std_instead_of_core::STD_INSTEAD_OF_ALLOC_INFO,
680680
crate::std_instead_of_core::STD_INSTEAD_OF_CORE_INFO,
681+
crate::std_wildcard_imports::STD_WILDCARD_IMPORTS_INFO,
681682
crate::string_patterns::MANUAL_PATTERN_CHAR_COMPARISON_INFO,
682683
crate::string_patterns::SINGLE_CHAR_PATTERN_INFO,
683684
crate::strings::STRING_ADD_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ mod size_of_in_element_count;
346346
mod size_of_ref;
347347
mod slow_vector_initialization;
348348
mod std_instead_of_core;
349+
mod std_wildcard_imports;
349350
mod string_patterns;
350351
mod strings;
351352
mod strlen_on_c_strings;
@@ -948,5 +949,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
948949
store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix));
949950
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
950951
store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom));
952+
store.register_late_pass(|_| Box::new(std_wildcard_imports::StdWildcardImports));
951953
// add lints here, do not remove this comment, it's used in `new_lint`
952954
}
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::*;

0 commit comments

Comments
 (0)