about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--book/src/lint_configuration.md1
-rw-r--r--clippy_dev/src/new_lint.rs63
-rw-r--r--clippy_lints/Cargo.toml3
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/manual_hash_one.rs133
-rw-r--r--clippy_lints/src/utils/conf.rs43
-rw-r--r--clippy_lints/src/wildcard_imports.rs1
-rw-r--r--clippy_utils/src/msrvs.rs2
-rw-r--r--clippy_utils/src/visitors.rs44
-rw-r--r--tests/ui/manual_hash_one.fixed89
-rw-r--r--tests/ui/manual_hash_one.rs89
-rw-r--r--tests/ui/manual_hash_one.stderr56
-rw-r--r--tests/ui/wildcard_imports.fixed28
-rw-r--r--tests/ui/wildcard_imports.rs28
-rw-r--r--tests/ui/wildcard_imports.stderr38
-rw-r--r--tests/ui/wildcard_imports_2021.edition2018.fixed28
-rw-r--r--tests/ui/wildcard_imports_2021.edition2018.stderr38
-rw-r--r--tests/ui/wildcard_imports_2021.edition2021.fixed28
-rw-r--r--tests/ui/wildcard_imports_2021.edition2021.stderr38
-rw-r--r--tests/ui/wildcard_imports_2021.rs28
22 files changed, 720 insertions, 62 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c9ab1e2402..db54bfbf0b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5072,6 +5072,7 @@ Released 2018-09-13
 [`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find
 [`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
 [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
+[`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
 [`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
 [`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
 [`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite
diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md
index b980083f1f5..2c958ccbbc2 100644
--- a/book/src/lint_configuration.md
+++ b/book/src/lint_configuration.md
@@ -151,6 +151,7 @@ The minimum rust version that the project supports
 * [`type_repetition_in_bounds`](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds)
 * [`tuple_array_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions)
 * [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold)
+* [`manual_hash_one`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one)
 
 
 ## `cognitive-complexity-threshold`
diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs
index e64cf2c8749..be2386bb1d2 100644
--- a/clippy_dev/src/new_lint.rs
+++ b/clippy_dev/src/new_lint.rs
@@ -58,7 +58,7 @@ pub fn create(
     };
 
     create_lint(&lint, msrv).context("Unable to create lint implementation")?;
-    create_test(&lint).context("Unable to create a test for the new lint")?;
+    create_test(&lint, msrv).context("Unable to create a test for the new lint")?;
 
     if lint.ty.is_none() {
         add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")?;
@@ -88,15 +88,21 @@ fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
     }
 }
 
-fn create_test(lint: &LintData<'_>) -> io::Result<()> {
-    fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
+fn create_test(lint: &LintData<'_>, msrv: bool) -> io::Result<()> {
+    fn create_project_layout<P: Into<PathBuf>>(
+        lint_name: &str,
+        location: P,
+        case: &str,
+        hint: &str,
+        msrv: bool,
+    ) -> io::Result<()> {
         let mut path = location.into().join(case);
         fs::create_dir(&path)?;
         write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;
 
         path.push("src");
         fs::create_dir(&path)?;
-        write_file(path.join("main.rs"), get_test_file_contents(lint_name))?;
+        write_file(path.join("main.rs"), get_test_file_contents(lint_name, msrv))?;
 
         Ok(())
     }
@@ -106,13 +112,25 @@ fn create_test(lint: &LintData<'_>) -> io::Result<()> {
         let test_dir = lint.project_root.join(&relative_test_dir);
         fs::create_dir(&test_dir)?;
 
-        create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
-        create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")?;
+        create_project_layout(
+            lint.name,
+            &test_dir,
+            "fail",
+            "Content that triggers the lint goes here",
+            msrv,
+        )?;
+        create_project_layout(
+            lint.name,
+            &test_dir,
+            "pass",
+            "This file should not trigger the lint",
+            false,
+        )?;
 
         println!("Generated test directories: `{relative_test_dir}/pass`, `{relative_test_dir}/fail`");
     } else {
         let test_path = format!("tests/ui/{}.rs", lint.name);
-        let test_contents = get_test_file_contents(lint.name);
+        let test_contents = get_test_file_contents(lint.name, msrv);
         write_file(lint.project_root.join(&test_path), test_contents)?;
 
         println!("Generated test file: `{test_path}`");
@@ -194,8 +212,8 @@ pub(crate) fn get_stabilization_version() -> String {
     parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`")
 }
 
-fn get_test_file_contents(lint_name: &str) -> String {
-    formatdoc!(
+fn get_test_file_contents(lint_name: &str, msrv: bool) -> String {
+    let mut test = formatdoc!(
         r#"
         #![warn(clippy::{lint_name})]
 
@@ -203,7 +221,29 @@ fn get_test_file_contents(lint_name: &str) -> String {
             // test code goes here
         }}
     "#
-    )
+    );
+
+    if msrv {
+        let _ = writedoc!(
+            test,
+            r#"
+
+                // TODO: set xx to the version one below the MSRV used by the lint, and yy to
+                // the version used by the lint
+                #[clippy::msrv = "1.xx"]
+                fn msrv_1_xx() {{
+                    // a simple example that would trigger the lint if the MSRV were met
+                }}
+
+                #[clippy::msrv = "1.yy"]
+                fn msrv_1_yy() {{
+                    // the same example as above
+                }}
+            "#
+        );
+    }
+
+    test
 }
 
 fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
@@ -258,7 +298,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
         )
     });
 
-    let _: fmt::Result = write!(result, "{}", get_lint_declaration(&name_upper, category));
+    let _: fmt::Result = writeln!(result, "{}", get_lint_declaration(&name_upper, category));
 
     result.push_str(&if enable_msrv {
         formatdoc!(
@@ -281,7 +321,6 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
             }}
 
             // TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
-            // TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
             // TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
         "#
         )
diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml
index dcd9a4adcbd..834753a2301 100644
--- a/clippy_lints/Cargo.toml
+++ b/clippy_lints/Cargo.toml
@@ -28,6 +28,9 @@ semver = "1.0"
 rustc-semver = "1.1"
 url = "2.2"
 
+[dev-dependencies]
+walkdir = "2.3"
+
 [features]
 deny-warnings = ["clippy_utils/deny-warnings"]
 # build clippy with internal lints enabled, off by default
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 4d1281ec1e7..b4b84b36044 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -280,6 +280,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::manual_clamp::MANUAL_CLAMP_INFO,
     crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
     crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
+    crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
     crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
     crate::manual_let_else::MANUAL_LET_ELSE_INFO,
     crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 1271be2fd93..63a3fbcb897 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -190,6 +190,7 @@ mod manual_async_fn;
 mod manual_bits;
 mod manual_clamp;
 mod manual_float_methods;
+mod manual_hash_one;
 mod manual_is_ascii_check;
 mod manual_let_else;
 mod manual_main_separator_str;
@@ -1119,6 +1120,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
             msrv(),
         ))
     });
+    store.register_late_pass(move |_| Box::new(manual_hash_one::ManualHashOne::new(msrv())));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
 
diff --git a/clippy_lints/src/manual_hash_one.rs b/clippy_lints/src/manual_hash_one.rs
new file mode 100644
index 00000000000..ea911335450
--- /dev/null
+++ b/clippy_lints/src/manual_hash_one.rs
@@ -0,0 +1,133 @@
+use clippy_utils::diagnostics::span_lint_hir_and_then;
+use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::visitors::{is_local_used, local_used_once};
+use clippy_utils::{is_trait_method, path_to_local_id};
+use rustc_errors::Applicability;
+use rustc_hir::{BindingAnnotation, ExprKind, Local, Node, PatKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for cases where [`BuildHasher::hash_one`] can be used.
+    ///
+    /// [`BuildHasher::hash_one`]: https://doc.rust-lang.org/std/hash/trait.BuildHasher.html#method.hash_one
+    ///
+    /// ### Why is this bad?
+    /// It is more concise to use the `hash_one` method.
+    ///
+    /// ### Example
+    /// ```rust
+    /// use std::hash::{BuildHasher, Hash, Hasher};
+    /// use std::collections::hash_map::RandomState;
+    ///
+    /// let s = RandomState::new();
+    /// let value = vec![1, 2, 3];
+    ///
+    /// let mut hasher = s.build_hasher();
+    /// value.hash(&mut hasher);
+    /// let hash = hasher.finish();
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// use std::hash::BuildHasher;
+    /// use std::collections::hash_map::RandomState;
+    ///
+    /// let s = RandomState::new();
+    /// let value = vec![1, 2, 3];
+    ///
+    /// let hash = s.hash_one(&value);
+    /// ```
+    #[clippy::version = "1.74.0"]
+    pub MANUAL_HASH_ONE,
+    complexity,
+    "manual implementations of `BuildHasher::hash_one`"
+}
+
+pub struct ManualHashOne {
+    msrv: Msrv,
+}
+
+impl ManualHashOne {
+    #[must_use]
+    pub fn new(msrv: Msrv) -> Self {
+        Self { msrv }
+    }
+}
+
+impl_lint_pass!(ManualHashOne => [MANUAL_HASH_ONE]);
+
+impl LateLintPass<'_> for ManualHashOne {
+    fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
+        // `let mut hasher = seg.build_hasher();`
+        if let PatKind::Binding(BindingAnnotation::MUT, hasher, _, None) = local.pat.kind
+            && let Some(init) = local.init
+            && !init.span.from_expansion()
+            && let ExprKind::MethodCall(seg, build_hasher, [], _) = init.kind
+            && seg.ident.name == sym!(build_hasher)
+
+            && let Node::Stmt(local_stmt) = cx.tcx.hir().get_parent(local.hir_id)
+            && let Node::Block(block) = cx.tcx.hir().get_parent(local_stmt.hir_id)
+
+            && let mut stmts = block.stmts.iter()
+                .skip_while(|stmt| stmt.hir_id != local_stmt.hir_id)
+                .skip(1)
+                .filter(|&stmt| is_local_used(cx, stmt, hasher))
+
+            // `hashed_value.hash(&mut hasher);`
+            && let Some(hash_stmt) = stmts.next()
+            && let StmtKind::Semi(hash_expr) = hash_stmt.kind
+            && !hash_expr.span.from_expansion()
+            && let ExprKind::MethodCall(seg, hashed_value, [ref_to_hasher], _) = hash_expr.kind
+            && seg.ident.name == sym::hash
+            && is_trait_method(cx, hash_expr, sym::Hash)
+            && path_to_local_id(ref_to_hasher.peel_borrows(), hasher)
+
+            && let maybe_finish_stmt = stmts.next()
+            // There should be no more statements referencing `hasher`
+            && stmts.next().is_none()
+
+            // `hasher.finish()`, may be anywhere in a statement or the trailing expr of the block
+            && let Some(path_expr) = local_used_once(cx, (maybe_finish_stmt, block.expr), hasher)
+            && let Node::Expr(finish_expr) = cx.tcx.hir().get_parent(path_expr.hir_id)
+            && !finish_expr.span.from_expansion()
+            && let ExprKind::MethodCall(seg, _, [], _) = finish_expr.kind
+            && seg.ident.name == sym!(finish)
+
+            && self.msrv.meets(msrvs::BUILD_HASHER_HASH_ONE)
+        {
+            span_lint_hir_and_then(
+                cx,
+                MANUAL_HASH_ONE,
+                finish_expr.hir_id,
+                finish_expr.span,
+                "manual implementation of `BuildHasher::hash_one`",
+                |diag| {
+                    if let Some(build_hasher) = snippet_opt(cx, build_hasher.span)
+                        && let Some(hashed_value) = snippet_opt(cx, hashed_value.span)
+                    {
+                        diag.multipart_suggestion(
+                            "try",
+                            vec![
+                                (local_stmt.span, String::new()),
+                                (hash_stmt.span, String::new()),
+                                (
+                                    finish_expr.span,
+                                    // `needless_borrows_for_generic_args` will take care of
+                                    // removing the `&` when it isn't needed
+                                    format!("{build_hasher}.hash_one(&{hashed_value})")
+                                )
+                            ],
+                            Applicability::MachineApplicable,
+                        );
+
+                    }
+                },
+            );
+        }
+    }
+
+    extract_msrv_attr!(LateContext);
+}
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs
index 75c3c7a958a..23da1de7730 100644
--- a/clippy_lints/src/utils/conf.rs
+++ b/clippy_lints/src/utils/conf.rs
@@ -294,7 +294,7 @@ define_Conf! {
     ///
     /// Suppress lints whenever the suggested change would cause breakage for other crates.
     (avoid_breaking_exported_api: bool = true),
-    /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD.
+    /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE.
     ///
     /// The minimum rust version that the project supports
     (msrv: Option<String> = None),
@@ -744,3 +744,44 @@ fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
 
     (rows, column_widths)
 }
+
+#[cfg(test)]
+mod tests {
+    use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+    use serde::de::IgnoredAny;
+    use std::fs;
+    use walkdir::WalkDir;
+
+    #[test]
+    fn configs_are_tested() {
+        let mut names: FxHashSet<String> = super::metadata::get_configuration_metadata()
+            .into_iter()
+            .map(|meta| meta.name.replace('_', "-"))
+            .collect();
+
+        let toml_files = WalkDir::new("../tests")
+            .into_iter()
+            .map(Result::unwrap)
+            .filter(|entry| entry.file_name() == "clippy.toml");
+
+        for entry in toml_files {
+            let file = fs::read_to_string(entry.path()).unwrap();
+            #[allow(clippy::zero_sized_map_values)]
+            if let Ok(map) = toml::from_str::<FxHashMap<String, IgnoredAny>>(&file) {
+                for name in map.keys() {
+                    names.remove(name.as_str());
+                }
+            }
+        }
+
+        assert!(
+            names.remove("allow-one-hash-in-raw-strings"),
+            "remove this when #11481 is fixed"
+        );
+
+        assert!(
+            names.is_empty(),
+            "Configuration variable lacks test: {names:?}\nAdd a test to `tests/ui-toml`"
+        );
+    }
+}
diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs
index d09d02a7dfd..70b83149ce1 100644
--- a/clippy_lints/src/wildcard_imports.rs
+++ b/clippy_lints/src/wildcard_imports.rs
@@ -132,6 +132,7 @@ impl LateLintPass<'_> for WildcardImports {
             if self.warn_on_all || !self.check_exceptions(item, use_path.segments);
             let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id);
             if !used_imports.is_empty(); // Already handled by `unused_imports`
+            if !used_imports.contains(&kw::Underscore);
             then {
                 let mut applicability = Applicability::MachineApplicable;
                 let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs
index 0faff05eb23..df839c2106f 100644
--- a/clippy_utils/src/msrvs.rs
+++ b/clippy_utils/src/msrvs.rs
@@ -19,7 +19,7 @@ macro_rules! msrv_aliases {
 
 // names may refer to stabilized feature flags or library items
 msrv_aliases! {
-    1,71,0 { TUPLE_ARRAY_CONVERSIONS }
+    1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
     1,70,0 { OPTION_IS_SOME_AND, BINARY_HEAP_RETAIN }
     1,68,0 { PATH_MAIN_SEPARATOR_STR }
     1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs
index 3b47a451345..d752fe7d97e 100644
--- a/clippy_utils/src/visitors.rs
+++ b/clippy_utils/src/visitors.rs
@@ -62,6 +62,27 @@ where
         }
     }
 }
+impl<'tcx, A, B> Visitable<'tcx> for (A, B)
+where
+    A: Visitable<'tcx>,
+    B: Visitable<'tcx>,
+{
+    fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
+        let (a, b) = self;
+        a.visit(visitor);
+        b.visit(visitor);
+    }
+}
+impl<'tcx, T> Visitable<'tcx> for Option<T>
+where
+    T: Visitable<'tcx>,
+{
+    fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
+        if let Some(x) = self {
+            x.visit(visitor);
+        }
+    }
+}
 macro_rules! visitable_ref {
     ($t:ident, $f:ident) => {
         impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
@@ -748,3 +769,26 @@ pub fn contains_break_or_continue(expr: &Expr<'_>) -> bool {
     })
     .is_some()
 }
+
+/// If the local is only used once in `visitable` returns the path expression referencing the given
+/// local
+pub fn local_used_once<'tcx>(
+    cx: &LateContext<'tcx>,
+    visitable: impl Visitable<'tcx>,
+    id: HirId,
+) -> Option<&'tcx Expr<'tcx>> {
+    let mut expr = None;
+
+    let cf = for_each_expr_with_closures(cx, visitable, |e| {
+        if path_to_local_id(e, id) && expr.replace(e).is_some() {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
+        }
+    });
+    if cf.is_some() {
+        return None;
+    }
+
+    expr
+}
diff --git a/tests/ui/manual_hash_one.fixed b/tests/ui/manual_hash_one.fixed
new file mode 100644
index 00000000000..edfd9c4a47b
--- /dev/null
+++ b/tests/ui/manual_hash_one.fixed
@@ -0,0 +1,89 @@
+#![warn(clippy::manual_hash_one)]
+#![allow(clippy::needless_borrows_for_generic_args)]
+
+use std::hash::{BuildHasher, Hash, Hasher};
+
+fn returned(b: impl BuildHasher) -> u64 {
+    
+    
+    b.hash_one(&true)
+}
+
+fn unsized_receiver(b: impl BuildHasher, s: &str) {
+    
+    
+    let _ = b.hash_one(&s[4..10]);
+}
+
+fn owned_value(b: impl BuildHasher, v: Vec<u32>) -> Vec<u32> {
+    
+    
+    let _ = b.hash_one(&v);
+    v
+}
+
+fn reused_hasher(b: impl BuildHasher) {
+    let mut hasher = b.build_hasher();
+    true.hash(&mut hasher);
+    let _ = hasher.finish();
+    let _ = hasher.finish();
+}
+
+fn reused_hasher_in_return(b: impl BuildHasher) -> u64 {
+    let mut hasher = b.build_hasher();
+    true.hash(&mut hasher);
+    let _ = hasher.finish();
+    hasher.finish()
+}
+
+fn no_hash(b: impl BuildHasher) {
+    let mut hasher = b.build_hasher();
+    let _ = hasher.finish();
+}
+
+fn hash_twice(b: impl BuildHasher) {
+    let mut hasher = b.build_hasher();
+    true.hash(&mut hasher);
+    true.hash(&mut hasher);
+    let _ = hasher.finish();
+}
+
+fn other_hasher(b: impl BuildHasher) {
+    let mut other_hasher = b.build_hasher();
+
+    let mut hasher = b.build_hasher();
+    true.hash(&mut other_hasher);
+    let _ = hasher.finish();
+}
+
+fn finish_then_hash(b: impl BuildHasher) {
+    let mut hasher = b.build_hasher();
+    let _ = hasher.finish();
+    true.hash(&mut hasher);
+}
+
+fn in_macro(b: impl BuildHasher) {
+    macro_rules! m {
+        ($b:expr) => {{
+            let mut hasher = $b.build_hasher();
+            true.hash(&mut hasher);
+            let _ = hasher.finish();
+        }};
+    }
+
+    m!(b);
+}
+
+#[clippy::msrv = "1.70"]
+fn msrv_1_70(b: impl BuildHasher, v: impl Hash) {
+    let mut hasher = b.build_hasher();
+    v.hash(&mut hasher);
+    let _ = hasher.finish();
+}
+
+#[clippy::msrv = "1.71"]
+fn msrv_1_71(b: impl BuildHasher, v: impl Hash) {
+    
+    
+    let _ = b.hash_one(&v);
+}
diff --git a/tests/ui/manual_hash_one.rs b/tests/ui/manual_hash_one.rs
new file mode 100644
index 00000000000..ee61522853f
--- /dev/null
+++ b/tests/ui/manual_hash_one.rs
@@ -0,0 +1,89 @@
+#![warn(clippy::manual_hash_one)]
+#![allow(clippy::needless_borrows_for_generic_args)]
+
+use std::hash::{BuildHasher, Hash, Hasher};
+
+fn returned(b: impl BuildHasher) -> u64 {
+    let mut hasher = b.build_hasher();
+    true.hash(&mut hasher);
+    hasher.finish()
+}
+
+fn unsized_receiver(b: impl BuildHasher, s: &str) {
+    let mut hasher = b.build_hasher();
+    s[4..10].hash(&mut hasher);
+    let _ = hasher.finish();
+}
+
+fn owned_value(b: impl BuildHasher, v: Vec<u32>) -> Vec<u32> {
+    let mut hasher = b.build_hasher();
+    v.hash(&mut hasher);
+    let _ = hasher.finish();
+    v
+}
+
+fn reused_hasher(b: impl BuildHasher) {
+    let mut hasher = b.build_hasher();
+    true.hash(&mut hasher);
+    let _ = hasher.finish();
+    let _ = hasher.finish();
+}
+
+fn reused_hasher_in_return(b: impl BuildHasher) -> u64 {
+    let mut hasher = b.build_hasher();
+    true.hash(&mut hasher);
+    let _ = hasher.finish();
+    hasher.finish()
+}
+
+fn no_hash(b: impl BuildHasher) {
+    let mut hasher = b.build_hasher();
+    let _ = hasher.finish();
+}
+
+fn hash_twice(b: impl BuildHasher) {
+    let mut hasher = b.build_hasher();
+    true.hash(&mut hasher);
+    true.hash(&mut hasher);
+    let _ = hasher.finish();
+}
+
+fn other_hasher(b: impl BuildHasher) {
+    let mut other_hasher = b.build_hasher();
+
+    let mut hasher = b.build_hasher();
+    true.hash(&mut other_hasher);
+    let _ = hasher.finish();
+}
+
+fn finish_then_hash(b: impl BuildHasher) {
+    let mut hasher = b.build_hasher();
+    let _ = hasher.finish();
+    true.hash(&mut hasher);
+}
+
+fn in_macro(b: impl BuildHasher) {
+    macro_rules! m {
+        ($b:expr) => {{
+            let mut hasher = $b.build_hasher();
+            true.hash(&mut hasher);
+            let _ = hasher.finish();
+        }};
+    }
+
+    m!(b);
+}
+
+#[clippy::msrv = "1.70"]
+fn msrv_1_70(b: impl BuildHasher, v: impl Hash) {
+    let mut hasher = b.build_hasher();
+    v.hash(&mut hasher);
+    let _ = hasher.finish();
+}
+
+#[clippy::msrv = "1.71"]
+fn msrv_1_71(b: impl BuildHasher, v: impl Hash) {
+    let mut hasher = b.build_hasher();
+    v.hash(&mut hasher);
+    let _ = hasher.finish();
+}
diff --git a/tests/ui/manual_hash_one.stderr b/tests/ui/manual_hash_one.stderr
new file mode 100644
index 00000000000..3ce6f41e1f9
--- /dev/null
+++ b/tests/ui/manual_hash_one.stderr
@@ -0,0 +1,56 @@
+error: manual implementation of `BuildHasher::hash_one`
+  --> $DIR/manual_hash_one.rs:9:5
+   |
+LL |     hasher.finish()
+   |     ^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::manual-hash-one` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::manual_hash_one)]`
+help: try
+   |
+LL ~     
+LL ~     
+LL ~     b.hash_one(&true)
+   |
+
+error: manual implementation of `BuildHasher::hash_one`
+  --> $DIR/manual_hash_one.rs:15:13
+   |
+LL |     let _ = hasher.finish();
+   |             ^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL ~     
+LL ~     
+LL ~     let _ = b.hash_one(&s[4..10]);
+   |
+
+error: manual implementation of `BuildHasher::hash_one`
+  --> $DIR/manual_hash_one.rs:21:13
+   |
+LL |     let _ = hasher.finish();
+   |             ^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL ~     
+LL ~     
+LL ~     let _ = b.hash_one(&v);
+   |
+
+error: manual implementation of `BuildHasher::hash_one`
+  --> $DIR/manual_hash_one.rs:88:13
+   |
+LL |     let _ = hasher.finish();
+   |             ^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL ~     
+LL ~     
+LL ~     let _ = b.hash_one(&v);
+   |
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui/wildcard_imports.fixed b/tests/ui/wildcard_imports.fixed
index 2828f9d048e..6fdd728b9b7 100644
--- a/tests/ui/wildcard_imports.fixed
+++ b/tests/ui/wildcard_imports.fixed
@@ -69,6 +69,34 @@ mod struct_mod {
     }
 }
 
+// issue 9942
+mod underscore_mod {
+    // allow use of `deref` so that `clippy --fix` includes `Deref`.
+    #![allow(noop_method_call)]
+
+    mod exports_underscore {
+        pub use std::ops::Deref as _;
+        pub fn dummy() {}
+    }
+
+    mod exports_underscore_ish {
+        pub use std::ops::Deref as _Deref;
+        pub fn dummy() {}
+    }
+
+    fn does_not_lint() {
+        use self::exports_underscore::*;
+        let _ = (&0).deref();
+        dummy();
+    }
+
+    fn does_lint() {
+        use self::exports_underscore_ish::{_Deref, dummy};
+        let _ = (&0).deref();
+        dummy();
+    }
+}
+
 fn main() {
     foo();
     multi_foo();
diff --git a/tests/ui/wildcard_imports.rs b/tests/ui/wildcard_imports.rs
index cbe70e505d8..20e06d4b366 100644
--- a/tests/ui/wildcard_imports.rs
+++ b/tests/ui/wildcard_imports.rs
@@ -69,6 +69,34 @@ mod struct_mod {
     }
 }
 
+// issue 9942
+mod underscore_mod {
+    // allow use of `deref` so that `clippy --fix` includes `Deref`.
+    #![allow(noop_method_call)]
+
+    mod exports_underscore {
+        pub use std::ops::Deref as _;
+        pub fn dummy() {}
+    }
+
+    mod exports_underscore_ish {
+        pub use std::ops::Deref as _Deref;
+        pub fn dummy() {}
+    }
+
+    fn does_not_lint() {
+        use self::exports_underscore::*;
+        let _ = (&0).deref();
+        dummy();
+    }
+
+    fn does_lint() {
+        use self::exports_underscore_ish::*;
+        let _ = (&0).deref();
+        dummy();
+    }
+}
+
 fn main() {
     foo();
     multi_foo();
diff --git a/tests/ui/wildcard_imports.stderr b/tests/ui/wildcard_imports.stderr
index 3c750815baf..01a5414778c 100644
--- a/tests/ui/wildcard_imports.stderr
+++ b/tests/ui/wildcard_imports.stderr
@@ -38,55 +38,61 @@ LL | use wildcard_imports_helper::*;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:97:13
+  --> $DIR/wildcard_imports.rs:94:13
+   |
+LL |         use self::exports_underscore_ish::*;
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `self::exports_underscore_ish::{_Deref, dummy}`
+
+error: usage of wildcard import
+  --> $DIR/wildcard_imports.rs:125:13
    |
 LL |         use crate::fn_mod::*;
    |             ^^^^^^^^^^^^^^^^ help: try: `crate::fn_mod::foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:103:75
+  --> $DIR/wildcard_imports.rs:131:75
    |
 LL |         use wildcard_imports_helper::inner::inner_for_self_import::{self, *};
    |                                                                           ^ help: try: `inner_extern_foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:104:13
+  --> $DIR/wildcard_imports.rs:132:13
    |
 LL |         use wildcard_imports_helper::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:116:20
+  --> $DIR/wildcard_imports.rs:144:20
    |
 LL |         use self::{inner::*, inner2::*};
    |                    ^^^^^^^^ help: try: `inner::inner_foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:116:30
+  --> $DIR/wildcard_imports.rs:144:30
    |
 LL |         use self::{inner::*, inner2::*};
    |                              ^^^^^^^^^ help: try: `inner2::inner_bar`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:123:13
+  --> $DIR/wildcard_imports.rs:151:13
    |
 LL |         use wildcard_imports_helper::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternExportedEnum, ExternExportedStruct, extern_exported}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:152:9
+  --> $DIR/wildcard_imports.rs:180:9
    |
 LL |     use crate::in_fn_test::*;
    |         ^^^^^^^^^^^^^^^^^^^^ help: try: `crate::in_fn_test::{ExportedEnum, ExportedStruct, exported}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:161:9
+  --> $DIR/wildcard_imports.rs:189:9
    |
 LL |     use crate:: in_fn_test::  * ;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate:: in_fn_test::exported`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:162:9
+  --> $DIR/wildcard_imports.rs:190:9
    |
 LL |       use crate:: fn_mod::
    |  _________^
@@ -94,40 +100,40 @@ LL | |         *;
    | |_________^ help: try: `crate:: fn_mod::foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:173:13
+  --> $DIR/wildcard_imports.rs:201:13
    |
 LL |         use super::*;
    |             ^^^^^^^^ help: try: `super::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:208:17
+  --> $DIR/wildcard_imports.rs:236:17
    |
 LL |             use super::*;
    |                 ^^^^^^^^ help: try: `super::insidefoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:216:13
+  --> $DIR/wildcard_imports.rs:244:13
    |
 LL |         use crate::super_imports::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate::super_imports::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:225:17
+  --> $DIR/wildcard_imports.rs:253:17
    |
 LL |             use super::super::*;
    |                 ^^^^^^^^^^^^^^^ help: try: `super::super::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:234:13
+  --> $DIR/wildcard_imports.rs:262:13
    |
 LL |         use super::super::super_imports::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `super::super::super_imports::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports.rs:242:13
+  --> $DIR/wildcard_imports.rs:270:13
    |
 LL |         use super::*;
    |             ^^^^^^^^ help: try: `super::foofoo`
 
-error: aborting due to 21 previous errors
+error: aborting due to 22 previous errors
 
diff --git a/tests/ui/wildcard_imports_2021.edition2018.fixed b/tests/ui/wildcard_imports_2021.edition2018.fixed
index b27281fa25c..6a9fe007d65 100644
--- a/tests/ui/wildcard_imports_2021.edition2018.fixed
+++ b/tests/ui/wildcard_imports_2021.edition2018.fixed
@@ -64,6 +64,34 @@ mod struct_mod {
     }
 }
 
+// issue 9942
+mod underscore_mod {
+    // allow use of `deref` so that `clippy --fix` includes `Deref`.
+    #![allow(noop_method_call)]
+
+    mod exports_underscore {
+        pub use std::ops::Deref as _;
+        pub fn dummy() {}
+    }
+
+    mod exports_underscore_ish {
+        pub use std::ops::Deref as _Deref;
+        pub fn dummy() {}
+    }
+
+    fn does_not_lint() {
+        use exports_underscore::*;
+        let _ = (&0).deref();
+        dummy();
+    }
+
+    fn does_lint() {
+        use exports_underscore_ish::{_Deref, dummy};
+        let _ = (&0).deref();
+        dummy();
+    }
+}
+
 fn main() {
     foo();
     multi_foo();
diff --git a/tests/ui/wildcard_imports_2021.edition2018.stderr b/tests/ui/wildcard_imports_2021.edition2018.stderr
index 709a665d65c..e39f240a4aa 100644
--- a/tests/ui/wildcard_imports_2021.edition2018.stderr
+++ b/tests/ui/wildcard_imports_2021.edition2018.stderr
@@ -38,55 +38,61 @@ LL | use wildcard_imports_helper::*;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:91:13
+  --> $DIR/wildcard_imports_2021.rs:89:13
+   |
+LL |         use exports_underscore_ish::*;
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `exports_underscore_ish::{_Deref, dummy}`
+
+error: usage of wildcard import
+  --> $DIR/wildcard_imports_2021.rs:119:13
    |
 LL |         use crate::fn_mod::*;
    |             ^^^^^^^^^^^^^^^^ help: try: `crate::fn_mod::foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:97:75
+  --> $DIR/wildcard_imports_2021.rs:125:75
    |
 LL |         use wildcard_imports_helper::inner::inner_for_self_import::{self, *};
    |                                                                           ^ help: try: `inner_extern_foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:98:13
+  --> $DIR/wildcard_imports_2021.rs:126:13
    |
 LL |         use wildcard_imports_helper::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:110:20
+  --> $DIR/wildcard_imports_2021.rs:138:20
    |
 LL |         use self::{inner::*, inner2::*};
    |                    ^^^^^^^^ help: try: `inner::inner_foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:110:30
+  --> $DIR/wildcard_imports_2021.rs:138:30
    |
 LL |         use self::{inner::*, inner2::*};
    |                              ^^^^^^^^^ help: try: `inner2::inner_bar`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:117:13
+  --> $DIR/wildcard_imports_2021.rs:145:13
    |
 LL |         use wildcard_imports_helper::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternExportedEnum, ExternExportedStruct, extern_exported}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:146:9
+  --> $DIR/wildcard_imports_2021.rs:174:9
    |
 LL |     use crate::in_fn_test::*;
    |         ^^^^^^^^^^^^^^^^^^^^ help: try: `crate::in_fn_test::{ExportedEnum, ExportedStruct, exported}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:155:9
+  --> $DIR/wildcard_imports_2021.rs:183:9
    |
 LL |     use crate:: in_fn_test::  * ;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate:: in_fn_test::exported`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:156:9
+  --> $DIR/wildcard_imports_2021.rs:184:9
    |
 LL |       use crate:: fn_mod::
    |  _________^
@@ -94,40 +100,40 @@ LL | |         *;
    | |_________^ help: try: `crate:: fn_mod::foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:167:13
+  --> $DIR/wildcard_imports_2021.rs:195:13
    |
 LL |         use super::*;
    |             ^^^^^^^^ help: try: `super::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:202:17
+  --> $DIR/wildcard_imports_2021.rs:230:17
    |
 LL |             use super::*;
    |                 ^^^^^^^^ help: try: `super::insidefoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:210:13
+  --> $DIR/wildcard_imports_2021.rs:238:13
    |
 LL |         use crate::super_imports::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate::super_imports::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:219:17
+  --> $DIR/wildcard_imports_2021.rs:247:17
    |
 LL |             use super::super::*;
    |                 ^^^^^^^^^^^^^^^ help: try: `super::super::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:228:13
+  --> $DIR/wildcard_imports_2021.rs:256:13
    |
 LL |         use super::super::super_imports::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `super::super::super_imports::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:236:13
+  --> $DIR/wildcard_imports_2021.rs:264:13
    |
 LL |         use super::*;
    |             ^^^^^^^^ help: try: `super::foofoo`
 
-error: aborting due to 21 previous errors
+error: aborting due to 22 previous errors
 
diff --git a/tests/ui/wildcard_imports_2021.edition2021.fixed b/tests/ui/wildcard_imports_2021.edition2021.fixed
index b27281fa25c..6a9fe007d65 100644
--- a/tests/ui/wildcard_imports_2021.edition2021.fixed
+++ b/tests/ui/wildcard_imports_2021.edition2021.fixed
@@ -64,6 +64,34 @@ mod struct_mod {
     }
 }
 
+// issue 9942
+mod underscore_mod {
+    // allow use of `deref` so that `clippy --fix` includes `Deref`.
+    #![allow(noop_method_call)]
+
+    mod exports_underscore {
+        pub use std::ops::Deref as _;
+        pub fn dummy() {}
+    }
+
+    mod exports_underscore_ish {
+        pub use std::ops::Deref as _Deref;
+        pub fn dummy() {}
+    }
+
+    fn does_not_lint() {
+        use exports_underscore::*;
+        let _ = (&0).deref();
+        dummy();
+    }
+
+    fn does_lint() {
+        use exports_underscore_ish::{_Deref, dummy};
+        let _ = (&0).deref();
+        dummy();
+    }
+}
+
 fn main() {
     foo();
     multi_foo();
diff --git a/tests/ui/wildcard_imports_2021.edition2021.stderr b/tests/ui/wildcard_imports_2021.edition2021.stderr
index 709a665d65c..e39f240a4aa 100644
--- a/tests/ui/wildcard_imports_2021.edition2021.stderr
+++ b/tests/ui/wildcard_imports_2021.edition2021.stderr
@@ -38,55 +38,61 @@ LL | use wildcard_imports_helper::*;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:91:13
+  --> $DIR/wildcard_imports_2021.rs:89:13
+   |
+LL |         use exports_underscore_ish::*;
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `exports_underscore_ish::{_Deref, dummy}`
+
+error: usage of wildcard import
+  --> $DIR/wildcard_imports_2021.rs:119:13
    |
 LL |         use crate::fn_mod::*;
    |             ^^^^^^^^^^^^^^^^ help: try: `crate::fn_mod::foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:97:75
+  --> $DIR/wildcard_imports_2021.rs:125:75
    |
 LL |         use wildcard_imports_helper::inner::inner_for_self_import::{self, *};
    |                                                                           ^ help: try: `inner_extern_foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:98:13
+  --> $DIR/wildcard_imports_2021.rs:126:13
    |
 LL |         use wildcard_imports_helper::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:110:20
+  --> $DIR/wildcard_imports_2021.rs:138:20
    |
 LL |         use self::{inner::*, inner2::*};
    |                    ^^^^^^^^ help: try: `inner::inner_foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:110:30
+  --> $DIR/wildcard_imports_2021.rs:138:30
    |
 LL |         use self::{inner::*, inner2::*};
    |                              ^^^^^^^^^ help: try: `inner2::inner_bar`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:117:13
+  --> $DIR/wildcard_imports_2021.rs:145:13
    |
 LL |         use wildcard_imports_helper::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternExportedEnum, ExternExportedStruct, extern_exported}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:146:9
+  --> $DIR/wildcard_imports_2021.rs:174:9
    |
 LL |     use crate::in_fn_test::*;
    |         ^^^^^^^^^^^^^^^^^^^^ help: try: `crate::in_fn_test::{ExportedEnum, ExportedStruct, exported}`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:155:9
+  --> $DIR/wildcard_imports_2021.rs:183:9
    |
 LL |     use crate:: in_fn_test::  * ;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate:: in_fn_test::exported`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:156:9
+  --> $DIR/wildcard_imports_2021.rs:184:9
    |
 LL |       use crate:: fn_mod::
    |  _________^
@@ -94,40 +100,40 @@ LL | |         *;
    | |_________^ help: try: `crate:: fn_mod::foo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:167:13
+  --> $DIR/wildcard_imports_2021.rs:195:13
    |
 LL |         use super::*;
    |             ^^^^^^^^ help: try: `super::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:202:17
+  --> $DIR/wildcard_imports_2021.rs:230:17
    |
 LL |             use super::*;
    |                 ^^^^^^^^ help: try: `super::insidefoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:210:13
+  --> $DIR/wildcard_imports_2021.rs:238:13
    |
 LL |         use crate::super_imports::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate::super_imports::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:219:17
+  --> $DIR/wildcard_imports_2021.rs:247:17
    |
 LL |             use super::super::*;
    |                 ^^^^^^^^^^^^^^^ help: try: `super::super::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:228:13
+  --> $DIR/wildcard_imports_2021.rs:256:13
    |
 LL |         use super::super::super_imports::*;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `super::super::super_imports::foofoo`
 
 error: usage of wildcard import
-  --> $DIR/wildcard_imports_2021.rs:236:13
+  --> $DIR/wildcard_imports_2021.rs:264:13
    |
 LL |         use super::*;
    |             ^^^^^^^^ help: try: `super::foofoo`
 
-error: aborting due to 21 previous errors
+error: aborting due to 22 previous errors
 
diff --git a/tests/ui/wildcard_imports_2021.rs b/tests/ui/wildcard_imports_2021.rs
index 7dd2103eca7..18ebc0f5127 100644
--- a/tests/ui/wildcard_imports_2021.rs
+++ b/tests/ui/wildcard_imports_2021.rs
@@ -64,6 +64,34 @@ mod struct_mod {
     }
 }
 
+// issue 9942
+mod underscore_mod {
+    // allow use of `deref` so that `clippy --fix` includes `Deref`.
+    #![allow(noop_method_call)]
+
+    mod exports_underscore {
+        pub use std::ops::Deref as _;
+        pub fn dummy() {}
+    }
+
+    mod exports_underscore_ish {
+        pub use std::ops::Deref as _Deref;
+        pub fn dummy() {}
+    }
+
+    fn does_not_lint() {
+        use exports_underscore::*;
+        let _ = (&0).deref();
+        dummy();
+    }
+
+    fn does_lint() {
+        use exports_underscore_ish::*;
+        let _ = (&0).deref();
+        dummy();
+    }
+}
+
 fn main() {
     foo();
     multi_foo();