From 80ac147ab1f28d204c742f582d9667a4abb63786 Mon Sep 17 00:00:00 2001
From: alanb <efik722life@gmail.com>
Date: Thu, 22 May 2025 19:42:23 +0900
Subject: [PATCH] Add new lint: `std_wildcard_imports`

---
 CHANGELOG.md                             |  1 +
 clippy_lints/src/declared_lints.rs       |  1 +
 clippy_lints/src/lib.rs                  |  2 +
 clippy_lints/src/std_wildcard_imports.rs | 92 ++++++++++++++++++++++++
 clippy_lints/src/wildcard_imports.rs     | 49 ++-----------
 clippy_utils/src/lib.rs                  | 57 ++++++++++++++-
 tests/ui/crashes/ice-11422.fixed         |  2 +-
 tests/ui/crashes/ice-11422.rs            |  2 +-
 tests/ui/enum_glob_use.fixed             |  2 +-
 tests/ui/enum_glob_use.rs                |  2 +-
 tests/ui/explicit_iter_loop.fixed        |  2 +-
 tests/ui/explicit_iter_loop.rs           |  2 +-
 tests/ui/for_kv_map.fixed                |  2 +-
 tests/ui/for_kv_map.rs                   |  2 +-
 tests/ui/into_iter_on_ref.fixed          |  2 +-
 tests/ui/into_iter_on_ref.rs             |  2 +-
 tests/ui/std_wildcard_imports.fixed      | 53 ++++++++++++++
 tests/ui/std_wildcard_imports.rs         | 53 ++++++++++++++
 tests/ui/std_wildcard_imports.stderr     | 41 +++++++++++
 19 files changed, 313 insertions(+), 56 deletions(-)
 create mode 100644 clippy_lints/src/std_wildcard_imports.rs
 create mode 100644 tests/ui/std_wildcard_imports.fixed
 create mode 100644 tests/ui/std_wildcard_imports.rs
 create mode 100644 tests/ui/std_wildcard_imports.stderr

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6db04a3d5257..e502c888867f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6291,6 +6291,7 @@ Released 2018-09-13
 [`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
 [`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
 [`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
+[`std_wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_wildcard_imports
 [`str_split_at_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_split_at_newline
 [`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
 [`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 71388779b08a..7086445ab02b 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -678,6 +678,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
     crate::std_instead_of_core::ALLOC_INSTEAD_OF_CORE_INFO,
     crate::std_instead_of_core::STD_INSTEAD_OF_ALLOC_INFO,
     crate::std_instead_of_core::STD_INSTEAD_OF_CORE_INFO,
+    crate::std_wildcard_imports::STD_WILDCARD_IMPORTS_INFO,
     crate::string_patterns::MANUAL_PATTERN_CHAR_COMPARISON_INFO,
     crate::string_patterns::SINGLE_CHAR_PATTERN_INFO,
     crate::strings::STRING_ADD_INFO,
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 6ec14486c20c..3adc0a4f31bd 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -346,6 +346,7 @@ mod size_of_in_element_count;
 mod size_of_ref;
 mod slow_vector_initialization;
 mod std_instead_of_core;
+mod std_wildcard_imports;
 mod string_patterns;
 mod strings;
 mod strlen_on_c_strings;
@@ -948,5 +949,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
     store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix));
     store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
     store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom));
+    store.register_late_pass(|_| Box::new(std_wildcard_imports::StdWildcardImports));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
diff --git a/clippy_lints/src/std_wildcard_imports.rs b/clippy_lints/src/std_wildcard_imports.rs
new file mode 100644
index 000000000000..f4f1c8ede3f9
--- /dev/null
+++ b/clippy_lints/src/std_wildcard_imports.rs
@@ -0,0 +1,92 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{is_prelude_import, sugg_glob_import, whole_glob_import_span};
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind, PathSegment, UseKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::declare_lint_pass;
+use rustc_span::sym;
+use rustc_span::symbol::{STDLIB_STABLE_CRATES, kw};
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for wildcard imports `use _::*` from the standard library crates.
+    ///
+    /// ### Why is this bad?
+    /// Wildcard imports can pollute the namespace. This is especially bad when importing from the
+    /// standard library through wildcards:
+    ///
+    /// ```no_run
+    /// use foo::bar; // Imports a function named bar
+    /// use std::rc::*; // Does not have a function named bar initially
+    ///
+    /// # mod foo { pub fn bar() {} }
+    /// bar();
+    /// ```
+    ///
+    /// When the `std::rc` module later adds a function named `bar`, the compiler cannot decide
+    /// which function to call, causing a compilation error.
+    ///
+    /// ### Exceptions
+    /// Wildcard imports are allowed from modules whose names contain `prelude`. Many crates
+    /// (including the standard library) provide modules named "prelude" specifically designed
+    /// for wildcard import.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// use std::rc::*;
+    ///
+    /// let _ = Rc::new(5);
+    /// ```
+    ///
+    /// Use instead:
+    /// ```no_run
+    /// use std::rc::Rc;
+    ///
+    /// let _ = Rc::new(5);
+    /// ```
+    #[clippy::version = "1.89.0"]
+    pub STD_WILDCARD_IMPORTS,
+    style,
+    "lint `use _::*` from the standard library crates"
+}
+
+declare_lint_pass!(StdWildcardImports => [STD_WILDCARD_IMPORTS]);
+
+impl LateLintPass<'_> for StdWildcardImports {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+        if let ItemKind::Use(use_path, UseKind::Glob) = item.kind
+            && !is_prelude_import(use_path.segments)
+            && is_std_import(use_path.segments)
+            && let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id)
+            && !used_imports.is_empty() // Already handled by `unused_imports`
+            && !used_imports.contains(&kw::Underscore)
+        {
+            let mut applicability = Applicability::MachineApplicable;
+            let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
+
+            let span = whole_glob_import_span(cx, item, import_source_snippet.is_empty())
+                .expect("Not a glob import statement");
+            let sugg = sugg_glob_import(&import_source_snippet, used_imports);
+
+            span_lint_and_sugg(
+                cx,
+                STD_WILDCARD_IMPORTS,
+                span,
+                "usage of wildcard import from `std` crates",
+                "try",
+                sugg,
+                applicability,
+            );
+        }
+    }
+}
+
+// Checks for the standard libraries, including `test` crate.
+fn is_std_import(segments: &[PathSegment<'_>]) -> bool {
+    let Some(first_segment_name) = segments.first().map(|ps| ps.ident.name) else {
+        return false;
+    };
+
+    STDLIB_STABLE_CRATES.contains(&first_segment_name) || first_segment_name == sym::test
+}
diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs
index 45a5dbabeb4e..abad813a8b28 100644
--- a/clippy_lints/src/wildcard_imports.rs
+++ b/clippy_lints/src/wildcard_imports.rs
@@ -1,7 +1,7 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::is_in_test;
-use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{is_in_test, is_prelude_import, sugg_glob_import, whole_glob_import_span};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
@@ -10,7 +10,6 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_middle::ty;
 use rustc_session::impl_lint_pass;
 use rustc_span::symbol::kw;
-use rustc_span::{BytePos, sym};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -136,38 +135,10 @@ impl LateLintPass<'_> for WildcardImports {
         {
             let mut applicability = Applicability::MachineApplicable;
             let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
-            let (span, braced_glob) = if import_source_snippet.is_empty() {
-                // This is a `_::{_, *}` import
-                // In this case `use_path.span` is empty and ends directly in front of the `*`,
-                // so we need to extend it by one byte.
-                (use_path.span.with_hi(use_path.span.hi() + BytePos(1)), true)
-            } else {
-                // In this case, the `use_path.span` ends right before the `::*`, so we need to
-                // extend it up to the `*`. Since it is hard to find the `*` in weird
-                // formatting like `use _ ::  *;`, we extend it up to, but not including the
-                // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
-                // can just use the end of the item span
-                let mut span = use_path.span.with_hi(item.span.hi());
-                if snippet(cx, span, "").ends_with(';') {
-                    span = use_path.span.with_hi(item.span.hi() - BytePos(1));
-                }
-                (span, false)
-            };
 
-            let mut imports: Vec<_> = used_imports.iter().map(ToString::to_string).collect();
-            let imports_string = if imports.len() == 1 {
-                imports.pop().unwrap()
-            } else if braced_glob {
-                imports.join(", ")
-            } else {
-                format!("{{{}}}", imports.join(", "))
-            };
-
-            let sugg = if braced_glob {
-                imports_string
-            } else {
-                format!("{import_source_snippet}::{imports_string}")
-            };
+            let span = whole_glob_import_span(cx, item, import_source_snippet.is_empty())
+                .expect("Not a glob import statement");
+            let sugg = sugg_glob_import(&import_source_snippet, used_imports);
 
             // Glob imports always have a single resolution.
             let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res[0] {
@@ -184,20 +155,12 @@ impl LateLintPass<'_> for WildcardImports {
 impl WildcardImports {
     fn check_exceptions(&self, cx: &LateContext<'_>, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
         item.span.from_expansion()
-            || is_prelude_import(segments)
+            || is_prelude_import(segments) // Many crates have a prelude, and it is imported as a glob by design.
             || is_allowed_via_config(segments, &self.allowed_segments)
             || (is_super_only_import(segments) && is_in_test(cx.tcx, item.hir_id()))
     }
 }
 
-// Allow "...prelude::..::*" imports.
-// Many crates have a prelude, and it is imported as a glob by design.
-fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
-    segments
-        .iter()
-        .any(|ps| ps.ident.as_str().contains(sym::prelude.as_str()))
-}
-
 // Allow "super::*" imports in tests.
 fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
     segments.len() == 1 && segments[0].ident.name == kw::Super
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index 55469e8ebc90..bf4f520d82f3 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -90,7 +90,7 @@ use itertools::Itertools;
 use rustc_abi::Integer;
 use rustc_ast::ast::{self, LitKind, RangeLimits};
 use rustc_attr_data_structures::{AttributeKind, find_attr};
-use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
 use rustc_data_structures::packed::Pu128;
 use rustc_data_structures::unhash::UnindexMap;
 use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
@@ -104,7 +104,7 @@ use rustc_hir::{
     CoroutineKind, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl,
     ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode,
     Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem,
-    TraitItemKind, TraitRef, TyKind, UnOp, def,
+    TraitItemKind, TraitRef, TyKind, UnOp, UseKind, def,
 };
 use rustc_lexer::{TokenKind, tokenize};
 use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -121,12 +121,13 @@ use rustc_middle::ty::{
 use rustc_span::hygiene::{ExpnKind, MacroKind};
 use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::{Ident, Symbol, kw};
-use rustc_span::{InnerSpan, Span};
+use rustc_span::{BytePos, InnerSpan, Span};
 use source::walk_span_to_context;
 use visitors::{Visitable, for_each_unconsumed_temporary};
 
 use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
 use crate::higher::Range;
+use crate::source::snippet;
 use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
 use crate::visitors::for_each_expr_without_closures;
 
@@ -3473,3 +3474,53 @@ pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
         None
     }
 }
+
+/// Returns true for `...prelude::...` imports.
+pub fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
+    segments
+        .iter()
+        .any(|ps| ps.ident.as_str().contains(sym::prelude.as_str()))
+}
+
+/// Returns the entire span for a given glob import statement, including the `*` symbol.
+pub fn whole_glob_import_span(cx: &LateContext<'_>, item: &Item<'_>, braced_glob: bool) -> Option<Span> {
+    let ItemKind::Use(use_path, UseKind::Glob) = item.kind else {
+        return None;
+    };
+
+    if braced_glob {
+        // This is a `_::{_, *}` import
+        // In this case `use_path.span` is empty and ends directly in front of the `*`,
+        // so we need to extend it by one byte.
+        Some(use_path.span.with_hi(use_path.span.hi() + BytePos(1)))
+    } else {
+        // In this case, the `use_path.span` ends right before the `::*`, so we need to
+        // extend it up to the `*`. Since it is hard to find the `*` in weird
+        // formatting like `use _ ::  *;`, we extend it up to, but not including the
+        // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
+        // can just use the end of the item span
+        let mut span = use_path.span.with_hi(item.span.hi());
+        if snippet(cx, span, "").ends_with(';') {
+            span = use_path.span.with_hi(item.span.hi() - BytePos(1));
+        }
+        Some(span)
+    }
+}
+
+/// Generates a suggestion for a glob import using only the actually used items.
+pub fn sugg_glob_import(import_source_snippet: &str, used_imports: &FxIndexSet<Symbol>) -> String {
+    let mut imports: Vec<_> = used_imports.iter().map(ToString::to_string).collect();
+    let imports_string = if imports.len() == 1 {
+        imports.pop().unwrap()
+    } else if import_source_snippet.is_empty() {
+        imports.join(", ")
+    } else {
+        format!("{{{}}}", imports.join(", "))
+    };
+
+    if import_source_snippet.is_empty() {
+        imports_string
+    } else {
+        format!("{import_source_snippet}::{imports_string}")
+    }
+}
diff --git a/tests/ui/crashes/ice-11422.fixed b/tests/ui/crashes/ice-11422.fixed
index be07a9d8c1f9..73e5072e3630 100644
--- a/tests/ui/crashes/ice-11422.fixed
+++ b/tests/ui/crashes/ice-11422.fixed
@@ -1,7 +1,7 @@
 #![warn(clippy::implied_bounds_in_impls)]
 
 use std::fmt::Debug;
-use std::ops::*;
+use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
 
 fn r#gen() -> impl PartialOrd + Debug {}
 //~^ implied_bounds_in_impls
diff --git a/tests/ui/crashes/ice-11422.rs b/tests/ui/crashes/ice-11422.rs
index 43de882caa11..9f2b38a0e029 100644
--- a/tests/ui/crashes/ice-11422.rs
+++ b/tests/ui/crashes/ice-11422.rs
@@ -1,7 +1,7 @@
 #![warn(clippy::implied_bounds_in_impls)]
 
 use std::fmt::Debug;
-use std::ops::*;
+use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
 
 fn r#gen() -> impl PartialOrd + PartialEq + Debug {}
 //~^ implied_bounds_in_impls
diff --git a/tests/ui/enum_glob_use.fixed b/tests/ui/enum_glob_use.fixed
index 881493f3d5ff..33fdc17eda54 100644
--- a/tests/ui/enum_glob_use.fixed
+++ b/tests/ui/enum_glob_use.fixed
@@ -1,5 +1,5 @@
 #![warn(clippy::enum_glob_use)]
-#![allow(unused)]
+#![allow(unused, clippy::std_wildcard_imports)]
 #![warn(unused_imports)]
 
 use std::cmp::Ordering::Less;
diff --git a/tests/ui/enum_glob_use.rs b/tests/ui/enum_glob_use.rs
index a510462ecb2f..b45db229c9dc 100644
--- a/tests/ui/enum_glob_use.rs
+++ b/tests/ui/enum_glob_use.rs
@@ -1,5 +1,5 @@
 #![warn(clippy::enum_glob_use)]
-#![allow(unused)]
+#![allow(unused, clippy::std_wildcard_imports)]
 #![warn(unused_imports)]
 
 use std::cmp::Ordering::*;
diff --git a/tests/ui/explicit_iter_loop.fixed b/tests/ui/explicit_iter_loop.fixed
index f246ec61800e..9cf181ef2bc9 100644
--- a/tests/ui/explicit_iter_loop.fixed
+++ b/tests/ui/explicit_iter_loop.fixed
@@ -10,7 +10,7 @@
 )]
 
 use core::slice;
-use std::collections::*;
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
 
 fn main() {
     let mut vec = vec![1, 2, 3, 4];
diff --git a/tests/ui/explicit_iter_loop.rs b/tests/ui/explicit_iter_loop.rs
index 35f4fb7097d8..457f3e3b33cd 100644
--- a/tests/ui/explicit_iter_loop.rs
+++ b/tests/ui/explicit_iter_loop.rs
@@ -10,7 +10,7 @@
 )]
 
 use core::slice;
-use std::collections::*;
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
 
 fn main() {
     let mut vec = vec![1, 2, 3, 4];
diff --git a/tests/ui/for_kv_map.fixed b/tests/ui/for_kv_map.fixed
index 2a68b7443fbf..0d89e20fe58c 100644
--- a/tests/ui/for_kv_map.fixed
+++ b/tests/ui/for_kv_map.fixed
@@ -1,7 +1,7 @@
 #![warn(clippy::for_kv_map)]
 #![allow(clippy::used_underscore_binding)]
 
-use std::collections::*;
+use std::collections::HashMap;
 use std::rc::Rc;
 
 fn main() {
diff --git a/tests/ui/for_kv_map.rs b/tests/ui/for_kv_map.rs
index 485a97815e3c..36dc773e21c2 100644
--- a/tests/ui/for_kv_map.rs
+++ b/tests/ui/for_kv_map.rs
@@ -1,7 +1,7 @@
 #![warn(clippy::for_kv_map)]
 #![allow(clippy::used_underscore_binding)]
 
-use std::collections::*;
+use std::collections::HashMap;
 use std::rc::Rc;
 
 fn main() {
diff --git a/tests/ui/into_iter_on_ref.fixed b/tests/ui/into_iter_on_ref.fixed
index 7626569fd422..b7f8bd2cf3c8 100644
--- a/tests/ui/into_iter_on_ref.fixed
+++ b/tests/ui/into_iter_on_ref.fixed
@@ -2,7 +2,7 @@
 #![warn(clippy::into_iter_on_ref)]
 
 struct X;
-use std::collections::*;
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
 
 fn main() {
     for _ in &[1, 2, 3] {}
diff --git a/tests/ui/into_iter_on_ref.rs b/tests/ui/into_iter_on_ref.rs
index 286a62c69ce5..6dacf59b459b 100644
--- a/tests/ui/into_iter_on_ref.rs
+++ b/tests/ui/into_iter_on_ref.rs
@@ -2,7 +2,7 @@
 #![warn(clippy::into_iter_on_ref)]
 
 struct X;
-use std::collections::*;
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
 
 fn main() {
     for _ in &[1, 2, 3] {}
diff --git a/tests/ui/std_wildcard_imports.fixed b/tests/ui/std_wildcard_imports.fixed
new file mode 100644
index 000000000000..c0d38e91a4df
--- /dev/null
+++ b/tests/ui/std_wildcard_imports.fixed
@@ -0,0 +1,53 @@
+//@aux-build:wildcard_imports_helper.rs
+
+#![feature(test)]
+#![warn(clippy::std_wildcard_imports)]
+
+extern crate alloc;
+extern crate proc_macro;
+extern crate test;
+extern crate wildcard_imports_helper;
+
+use alloc::boxed::Box;
+//~^ std_wildcard_imports
+use core::cell::Cell;
+//~^ std_wildcard_imports
+use proc_macro::is_available;
+//~^ std_wildcard_imports
+use std::any::type_name;
+//~^ std_wildcard_imports
+use std::mem::{swap, align_of};
+//~^ std_wildcard_imports
+use test::bench::black_box;
+//~^ std_wildcard_imports
+
+use crate::fn_mod::*;
+
+use wildcard_imports_helper::*;
+
+use std::io::prelude::*;
+use wildcard_imports_helper::extern_prelude::v1::*;
+use wildcard_imports_helper::prelude::v1::*;
+
+mod fn_mod {
+    pub fn foo() {}
+}
+
+mod super_imports {
+    use super::*;
+
+    fn test_super() {
+        fn_mod::foo();
+    }
+}
+
+fn main() {
+    foo();
+
+    let _ = Box::new(()); // imported from alloc::boxed module
+    let _ = Cell::new(()); // imported from core::cell module
+    let _ = is_available(); // imported from proc_macro crate
+    let _ = type_name::<i32>(); // imported from std::any module
+    let _ = align_of::<i32>(); // imported from std::mem module
+    black_box(()); // imported from test crate
+}
diff --git a/tests/ui/std_wildcard_imports.rs b/tests/ui/std_wildcard_imports.rs
new file mode 100644
index 000000000000..b8e584ecaa9d
--- /dev/null
+++ b/tests/ui/std_wildcard_imports.rs
@@ -0,0 +1,53 @@
+//@aux-build:wildcard_imports_helper.rs
+
+#![feature(test)]
+#![warn(clippy::std_wildcard_imports)]
+
+extern crate alloc;
+extern crate proc_macro;
+extern crate test;
+extern crate wildcard_imports_helper;
+
+use alloc::boxed::*;
+//~^ std_wildcard_imports
+use core::cell::*;
+//~^ std_wildcard_imports
+use proc_macro::*;
+//~^ std_wildcard_imports
+use std::any::*;
+//~^ std_wildcard_imports
+use std::mem::{swap, *};
+//~^ std_wildcard_imports
+use test::bench::*;
+//~^ std_wildcard_imports
+
+use crate::fn_mod::*;
+
+use wildcard_imports_helper::*;
+
+use std::io::prelude::*;
+use wildcard_imports_helper::extern_prelude::v1::*;
+use wildcard_imports_helper::prelude::v1::*;
+
+mod fn_mod {
+    pub fn foo() {}
+}
+
+mod super_imports {
+    use super::*;
+
+    fn test_super() {
+        fn_mod::foo();
+    }
+}
+
+fn main() {
+    foo();
+
+    let _ = Box::new(()); // imported from alloc::boxed module
+    let _ = Cell::new(()); // imported from core::cell module
+    let _ = is_available(); // imported from proc_macro crate
+    let _ = type_name::<i32>(); // imported from std::any module
+    let _ = align_of::<i32>(); // imported from std::mem module
+    black_box(()); // imported from test crate
+}
diff --git a/tests/ui/std_wildcard_imports.stderr b/tests/ui/std_wildcard_imports.stderr
new file mode 100644
index 000000000000..3b91a31410c5
--- /dev/null
+++ b/tests/ui/std_wildcard_imports.stderr
@@ -0,0 +1,41 @@
+error: usage of wildcard import from `std` crates
+  --> tests/ui/std_wildcard_imports.rs:11:5
+   |
+LL | use alloc::boxed::*;
+   |     ^^^^^^^^^^^^^^^ help: try: `alloc::boxed::Box`
+   |
+   = note: `-D clippy::std-wildcard-imports` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::std_wildcard_imports)]`
+
+error: usage of wildcard import from `std` crates
+  --> tests/ui/std_wildcard_imports.rs:13:5
+   |
+LL | use core::cell::*;
+   |     ^^^^^^^^^^^^^ help: try: `core::cell::Cell`
+
+error: usage of wildcard import from `std` crates
+  --> tests/ui/std_wildcard_imports.rs:15:5
+   |
+LL | use proc_macro::*;
+   |     ^^^^^^^^^^^^^ help: try: `proc_macro::is_available`
+
+error: usage of wildcard import from `std` crates
+  --> tests/ui/std_wildcard_imports.rs:17:5
+   |
+LL | use std::any::*;
+   |     ^^^^^^^^^^^ help: try: `std::any::type_name`
+
+error: usage of wildcard import from `std` crates
+  --> tests/ui/std_wildcard_imports.rs:19:22
+   |
+LL | use std::mem::{swap, *};
+   |                      ^ help: try: `align_of`
+
+error: usage of wildcard import from `std` crates
+  --> tests/ui/std_wildcard_imports.rs:21:5
+   |
+LL | use test::bench::*;
+   |     ^^^^^^^^^^^^^^ help: try: `test::bench::black_box`
+
+error: aborting due to 6 previous errors
+