about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_lint/src/context.rs37
-rw-r--r--compiler/rustc_lint/src/early.rs1
-rw-r--r--compiler/rustc_lint/src/levels.rs10
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs16
-rw-r--r--compiler/rustc_resolve/src/late.rs178
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs78
-rw-r--r--compiler/rustc_resolve/src/late/lifetimes.rs379
-rw-r--r--src/test/ui/async-await/unused-lifetime.rs16
-rw-r--r--src/test/ui/async-await/unused-lifetime.stderr34
-rw-r--r--src/test/ui/single-use-lifetime/fn-types.stderr5
-rw-r--r--src/test/ui/single-use-lifetime/one-use-in-fn-argument.rs13
-rw-r--r--src/test/ui/single-use-lifetime/one-use-in-fn-return.rs6
-rw-r--r--src/test/ui/single-use-lifetime/one-use-in-inherent-impl-header.stderr5
-rw-r--r--src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.rs1
-rw-r--r--src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.stderr34
-rw-r--r--src/test/ui/single-use-lifetime/one-use-in-inherent-method-return.stderr5
-rw-r--r--src/test/ui/single-use-lifetime/one-use-in-struct.rs3
-rw-r--r--src/test/ui/single-use-lifetime/one-use-in-trait-method-argument.rs7
-rw-r--r--src/test/ui/single-use-lifetime/two-uses-in-inherent-method-argument-and-return.stderr5
19 files changed, 364 insertions, 469 deletions
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index d7cd5ec04f3..2c6bdef361a 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -819,6 +819,43 @@ pub trait LintContext: Sized {
                         "see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information",
                     );
                 },
+                BuiltinLintDiagnostics::SingleUseLifetime {
+                    param_span,
+                    use_span: Some((use_span, elide)),
+                    deletion_span,
+                } => {
+                    debug!(?param_span, ?use_span, ?deletion_span);
+                    db.span_label(param_span, "this lifetime...");
+                    db.span_label(use_span, "...is used only here");
+                    let msg = "elide the single-use lifetime";
+                    let (use_span, replace_lt) = if elide {
+                        let use_span = sess.source_map().span_extend_while(
+                            use_span,
+                            char::is_whitespace,
+                        ).unwrap_or(use_span);
+                        (use_span, String::new())
+                    } else {
+                        (use_span, "'_".to_owned())
+                    };
+                    db.multipart_suggestion(
+                        msg,
+                        vec![(deletion_span, String::new()), (use_span, replace_lt)],
+                        Applicability::MachineApplicable,
+                    );
+                },
+                BuiltinLintDiagnostics::SingleUseLifetime {
+                    param_span: _,
+                    use_span: None,
+                    deletion_span,
+                } => {
+                    debug!(?deletion_span);
+                    db.span_suggestion(
+                        deletion_span,
+                        "elide the unused lifetime",
+                        String::new(),
+                        Applicability::MachineApplicable,
+                    );
+                },
             }
             // Rewrap `db`, and pass control to the user.
             decorate(LintDiagnosticBuilder::new(db));
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
index fecbd4ae544..8e505152fc4 100644
--- a/compiler/rustc_lint/src/early.rs
+++ b/compiler/rustc_lint/src/early.rs
@@ -239,6 +239,7 @@ impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T>
 
     fn visit_generic_param(&mut self, param: &'a ast::GenericParam) {
         run_early_pass!(self, check_generic_param, param);
+        self.check_id(param.id);
         ast_visit::walk_generic_param(self, param);
     }
 
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 257549bf1a1..54f2c725279 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -14,7 +14,7 @@ use rustc_middle::lint::{
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::{RegisteredTools, TyCtxt};
 use rustc_session::lint::{
-    builtin::{self, FORBIDDEN_LINT_GROUPS, UNFULFILLED_LINT_EXPECTATIONS},
+    builtin::{self, FORBIDDEN_LINT_GROUPS, SINGLE_USE_LIFETIMES, UNFULFILLED_LINT_EXPECTATIONS},
     Level, Lint, LintExpectationId, LintId,
 };
 use rustc_session::parse::{add_feature_diagnostics, feature_err};
@@ -259,6 +259,14 @@ impl<'s> LintLevelsBuilder<'s> {
         let sess = self.sess;
         let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
         for (attr_index, attr) in attrs.iter().enumerate() {
+            if attr.has_name(sym::automatically_derived) {
+                self.current_specs_mut().insert(
+                    LintId::of(SINGLE_USE_LIFETIMES),
+                    (Level::Allow, LintLevelSource::Default),
+                );
+                continue;
+            }
+
             let level = match Level::from_attr(attr) {
                 None => continue,
                 Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 57b4f96dc10..e50abf6cf25 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -423,7 +423,11 @@ pub enum BuiltinLintDiagnostics {
     DeprecatedMacro(Option<Symbol>, Span),
     MissingAbi(Span, Abi),
     UnusedDocComment(Span),
-    UnusedBuiltinAttribute { attr_name: Symbol, macro_name: String, invoc_span: Span },
+    UnusedBuiltinAttribute {
+        attr_name: Symbol,
+        macro_name: String,
+        invoc_span: Span,
+    },
     PatternsInFnsWithoutBody(Span, Ident),
     LegacyDeriveHelpers(Span),
     ProcMacroBackCompat(String),
@@ -435,6 +439,16 @@ pub enum BuiltinLintDiagnostics {
     UnicodeTextFlow(Span, String),
     UnexpectedCfg((Symbol, Span), Option<(Symbol, Span)>),
     DeprecatedWhereclauseLocation(Span, String),
+    SingleUseLifetime {
+        /// Span of the parameter which declares this lifetime.
+        param_span: Span,
+        /// Span of the code that should be removed when eliding this lifetime.
+        /// This span should include leading or trailing comma.
+        deletion_span: Span,
+        /// Span of the single use, or None if the lifetime is never used.
+        /// If true, the lifetime will be fully elided.
+        use_span: Option<(Span, bool)>,
+    },
 }
 
 /// Lints that are buffered up early on in the `Session` before the
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index bf7bdecf1f4..f05090d046f 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -1,3 +1,4 @@
+// ignore-tidy-filelength
 //! "Late resolution" is the pass that resolves most of names in a crate beside imports and macros.
 //! It runs when the crate is fully expanded and its module structure is fully built.
 //! So it just walks through the crate and resolves all the expressions, types, etc.
@@ -19,7 +20,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
 use rustc_errors::DiagnosticId;
 use rustc_hir::def::Namespace::{self, *};
 use rustc_hir::def::{self, CtorKind, DefKind, PartialRes, PerNS};
-use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
+use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID};
 use rustc_hir::definitions::DefPathData;
 use rustc_hir::{PrimTy, TraitCandidate};
 use rustc_index::vec::Idx;
@@ -197,13 +198,19 @@ impl<'a, R> Rib<'a, R> {
     }
 }
 
+#[derive(Clone, Copy, Debug)]
+enum LifetimeUseSet {
+    One { use_span: Span, use_ctxt: visit::LifetimeCtxt },
+    Many,
+}
+
 #[derive(Copy, Clone, Debug)]
 enum LifetimeRibKind {
     /// This rib acts as a barrier to forbid reference to lifetimes of a parent item.
     Item,
 
     /// This rib declares generic parameters.
-    Generics { parent: NodeId, span: Span, kind: LifetimeBinderKind },
+    Generics { binder: NodeId, span: Span, kind: LifetimeBinderKind },
 
     /// FIXME(const_generics): This patches over an ICE caused by non-'static lifetimes in const
     /// generics. We are disallowing this until we can decide on how we want to handle non-'static
@@ -230,7 +237,7 @@ enum LifetimeRibKind {
     AnonymousReportError,
 
     /// Pass responsibility to `resolve_lifetime` code for all cases.
-    AnonymousPassThrough(NodeId),
+    AnonymousPassThrough(NodeId, /* in_fn_return */ bool),
 }
 
 #[derive(Copy, Clone, Debug)]
@@ -519,6 +526,9 @@ struct LateResolutionVisitor<'a, 'b, 'ast> {
     /// In most cases this will be `None`, in which case errors will always be reported.
     /// If it is `true`, then it will be updated when entering a nested function or trait body.
     in_func_body: bool,
+
+    /// Count the number of places a lifetime is used.
+    lifetime_uses: FxHashMap<LocalDefId, LifetimeUseSet>,
 }
 
 /// Walks the whole crate in DFS order, visiting each item, resolving names as it goes.
@@ -599,17 +609,19 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
                     &bare_fn.generic_params,
                     NormalRibKind,
                     LifetimeRibKind::Generics {
-                        parent: ty.id,
+                        binder: ty.id,
                         kind: LifetimeBinderKind::BareFnType,
                         span,
                     },
                     |this| {
+                        this.visit_generic_param_vec(&bare_fn.generic_params, false);
                         this.with_lifetime_rib(
-                            LifetimeRibKind::AnonymousPassThrough(ty.id),
-                            |this| {
-                                this.visit_generic_param_vec(&bare_fn.generic_params, false);
-                                visit::walk_fn_decl(this, &bare_fn.decl);
-                            },
+                            LifetimeRibKind::AnonymousPassThrough(ty.id, false),
+                            |this| walk_list!(this, visit_param, &bare_fn.decl.inputs),
+                        );
+                        this.with_lifetime_rib(
+                            LifetimeRibKind::AnonymousPassThrough(ty.id, true),
+                            |this| this.visit_fn_ret_ty(&bare_fn.decl.output),
                         );
                     },
                 );
@@ -628,7 +640,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
             &tref.bound_generic_params,
             NormalRibKind,
             LifetimeRibKind::Generics {
-                parent: tref.trait_ref.ref_id,
+                binder: tref.trait_ref.ref_id,
                 kind: LifetimeBinderKind::PolyTrait,
                 span,
             },
@@ -652,7 +664,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
                         &generics.params,
                         ItemRibKind(HasGenericParams::Yes),
                         LifetimeRibKind::Generics {
-                            parent: foreign_item.id,
+                            binder: foreign_item.id,
                             kind: LifetimeBinderKind::Item,
                             span: generics.span,
                         },
@@ -666,7 +678,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
                         &generics.params,
                         ItemRibKind(HasGenericParams::Yes),
                         LifetimeRibKind::Generics {
-                            parent: foreign_item.id,
+                            binder: foreign_item.id,
                             kind: LifetimeBinderKind::Function,
                             span: generics.span,
                         },
@@ -690,13 +702,20 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
             // a body, or if there's no body for some other reason.
             FnKind::Fn(FnCtxt::Foreign, _, sig, _, generics, _)
             | FnKind::Fn(_, _, sig, _, generics, None) => {
-                self.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(fn_id), |this| {
-                    // We don't need to deal with patterns in parameters, because
-                    // they are not possible for foreign or bodiless functions.
-                    this.visit_fn_header(&sig.header);
-                    this.visit_generics(generics);
-                    visit::walk_fn_decl(this, &sig.decl);
-                });
+                // We don't need to deal with patterns in parameters, because
+                // they are not possible for foreign or bodiless functions.
+                self.with_lifetime_rib(
+                    LifetimeRibKind::AnonymousPassThrough(fn_id, false),
+                    |this| {
+                        this.visit_fn_header(&sig.header);
+                        this.visit_generics(generics);
+                        walk_list!(this, visit_param, &sig.decl.inputs);
+                    },
+                );
+                self.with_lifetime_rib(
+                    LifetimeRibKind::AnonymousPassThrough(fn_id, true),
+                    |this| this.visit_fn_ret_ty(&sig.decl.output),
+                );
                 return;
             }
             FnKind::Fn(FnCtxt::Free, ..) => FnItemRibKind,
@@ -759,28 +778,32 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
                     this.r.extra_lifetime_params_map.insert(async_node_id, extra_lifetime_params);
 
                     this.with_lifetime_rib(
-                        LifetimeRibKind::AnonymousPassThrough(async_node_id),
+                        LifetimeRibKind::AnonymousPassThrough(async_node_id, true),
                         |this| visit::walk_fn_ret_ty(this, &declaration.output),
                     );
                 } else {
-                    this.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(fn_id), |this| {
-                        // Add each argument to the rib.
-                        this.resolve_params(&declaration.inputs);
-
-                        visit::walk_fn_ret_ty(this, &declaration.output);
-                    });
+                    // Add each argument to the rib.
+                    this.with_lifetime_rib(
+                        LifetimeRibKind::AnonymousPassThrough(fn_id, false),
+                        |this| this.resolve_params(&declaration.inputs),
+                    );
+                    this.with_lifetime_rib(
+                        LifetimeRibKind::AnonymousPassThrough(fn_id, true),
+                        |this| visit::walk_fn_ret_ty(this, &declaration.output),
+                    );
                 };
 
                 // Ignore errors in function bodies if this is rustdoc
                 // Be sure not to set this until the function signature has been resolved.
                 let previous_state = replace(&mut this.in_func_body, true);
                 // Resolve the function body, potentially inside the body of an async closure
-                this.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(fn_id), |this| {
-                    match fn_kind {
+                this.with_lifetime_rib(
+                    LifetimeRibKind::AnonymousPassThrough(fn_id, false),
+                    |this| match fn_kind {
                         FnKind::Fn(.., body) => walk_list!(this, visit_block, body),
                         FnKind::Closure(_, body) => this.visit_expr(body),
-                    }
-                });
+                    },
+                );
 
                 debug!("(resolving function) leaving function");
                 this.in_func_body = previous_state;
@@ -788,8 +811,8 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
         });
         self.diagnostic_metadata.current_function = previous_value;
     }
-    fn visit_lifetime(&mut self, lifetime: &'ast Lifetime, _: visit::LifetimeCtxt) {
-        self.resolve_lifetime(lifetime)
+    fn visit_lifetime(&mut self, lifetime: &'ast Lifetime, use_ctxt: visit::LifetimeCtxt) {
+        self.resolve_lifetime(lifetime, use_ctxt)
     }
 
     fn visit_generics(&mut self, generics: &'ast Generics) {
@@ -864,10 +887,16 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
         if let Some(ref args) = path_segment.args {
             match &**args {
                 GenericArgs::AngleBracketed(..) => visit::walk_generic_args(self, path_span, args),
-                GenericArgs::Parenthesized(..) => self.with_lifetime_rib(
-                    LifetimeRibKind::AnonymousPassThrough(path_segment.id),
-                    |this| visit::walk_generic_args(this, path_span, args),
-                ),
+                GenericArgs::Parenthesized(ref data) => {
+                    self.with_lifetime_rib(
+                        LifetimeRibKind::AnonymousPassThrough(path_segment.id, false),
+                        |this| walk_list!(this, visit_ty, &data.inputs),
+                    );
+                    self.with_lifetime_rib(
+                        LifetimeRibKind::AnonymousPassThrough(path_segment.id, true),
+                        |this| visit::walk_fn_ret_ty(this, &data.output),
+                    )
+                }
             }
         }
     }
@@ -890,7 +919,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
                     &bound_generic_params,
                     NormalRibKind,
                     LifetimeRibKind::Generics {
-                        parent: bounded_ty.id,
+                        binder: bounded_ty.id,
                         kind: LifetimeBinderKind::WhereBound,
                         span,
                     },
@@ -971,6 +1000,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
             diagnostic_metadata: DiagnosticMetadata::default(),
             // errors at module scope should always be reported
             in_func_body: false,
+            lifetime_uses: Default::default(),
         }
     }
 
@@ -1178,7 +1208,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
     }
 
     #[tracing::instrument(level = "debug", skip(self))]
-    fn resolve_lifetime(&mut self, lifetime: &'ast Lifetime) {
+    fn resolve_lifetime(&mut self, lifetime: &'ast Lifetime, use_ctxt: visit::LifetimeCtxt) {
         let ident = lifetime.ident;
 
         if ident.name == kw::StaticLifetime {
@@ -1196,6 +1226,40 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
             let normalized_ident = ident.normalize_to_macros_2_0();
             if let Some(&(_, region)) = rib.bindings.get(&normalized_ident) {
                 self.record_lifetime_res(lifetime.id, region);
+
+                if let LifetimeRes::Param { param, .. } = region {
+                    match self.lifetime_uses.entry(param) {
+                        Entry::Vacant(v) => {
+                            debug!("First use of {:?} at {:?}", region, ident.span);
+                            let use_set = self
+                                .lifetime_ribs
+                                .iter()
+                                .rev()
+                                .find_map(|rib| match rib.kind {
+                                    // Do not suggest eliding a lifetime where an anonymous
+                                    // lifetime would be illegal.
+                                    LifetimeRibKind::Item
+                                    | LifetimeRibKind::AnonymousPassThrough(_, true)
+                                    | LifetimeRibKind::AnonymousReportError => {
+                                        Some(LifetimeUseSet::Many)
+                                    }
+                                    // An anonymous lifetime is legal here, go ahead.
+                                    LifetimeRibKind::AnonymousPassThrough(_, false)
+                                    | LifetimeRibKind::AnonymousCreateParameter(_) => {
+                                        Some(LifetimeUseSet::One { use_span: ident.span, use_ctxt })
+                                    }
+                                    _ => None,
+                                })
+                                .unwrap_or(LifetimeUseSet::Many);
+                            debug!(?use_ctxt, ?use_set);
+                            v.insert(use_set);
+                        }
+                        Entry::Occupied(mut o) => {
+                            debug!("Many uses of {:?} at {:?}", region, ident.span);
+                            *o.get_mut() = LifetimeUseSet::Many;
+                        }
+                    }
+                }
                 return;
             }
 
@@ -1262,7 +1326,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                     self.record_lifetime_res(lifetime.id, LifetimeRes::Error);
                     return;
                 }
-                LifetimeRibKind::AnonymousPassThrough(node_id) => {
+                LifetimeRibKind::AnonymousPassThrough(node_id, _) => {
                     self.record_lifetime_res(
                         lifetime.id,
                         LifetimeRes::Anonymous { binder: node_id, elided },
@@ -1382,7 +1446,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                     // `PathSegment`, for which there is no associated `'_` or `&T` with no explicit
                     // lifetime. Instead, we simply create an implicit lifetime, which will be checked
                     // later, at which point a suitable error will be emitted.
-                    LifetimeRibKind::AnonymousPassThrough(binder) => {
+                    LifetimeRibKind::AnonymousPassThrough(binder, _) => {
                         res = LifetimeRes::Anonymous { binder, elided: true };
                         break;
                     }
@@ -1550,7 +1614,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                 &generics.params,
                 ItemRibKind(HasGenericParams::Yes),
                 LifetimeRibKind::Generics {
-                    parent: item.id,
+                    binder: item.id,
                     kind: LifetimeBinderKind::Item,
                     span: generics.span,
                 },
@@ -1620,7 +1684,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                     &generics.params,
                     ItemRibKind(HasGenericParams::Yes),
                     LifetimeRibKind::Generics {
-                        parent: item.id,
+                        binder: item.id,
                         kind: LifetimeBinderKind::Item,
                         span: generics.span,
                     },
@@ -1633,7 +1697,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                     &generics.params,
                     ItemRibKind(HasGenericParams::Yes),
                     LifetimeRibKind::Generics {
-                        parent: item.id,
+                        binder: item.id,
                         kind: LifetimeBinderKind::Function,
                         span: generics.span,
                     },
@@ -1665,7 +1729,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                     &generics.params,
                     ItemRibKind(HasGenericParams::Yes),
                     LifetimeRibKind::Generics {
-                        parent: item.id,
+                        binder: item.id,
                         kind: LifetimeBinderKind::Item,
                         span: generics.span,
                     },
@@ -1686,7 +1750,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                                             &generics.params,
                                             AssocItemRibKind,
                                             LifetimeRibKind::Generics {
-                                                parent: item.id,
+                                                binder: item.id,
                                                 span: generics.span,
                                                 kind,
                                             },
@@ -1754,7 +1818,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                     &generics.params,
                     ItemRibKind(HasGenericParams::Yes),
                     LifetimeRibKind::Generics {
-                        parent: item.id,
+                        binder: item.id,
                         kind: LifetimeBinderKind::Item,
                         span: generics.span,
                     },
@@ -1824,6 +1888,9 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
         F: FnOnce(&mut Self),
     {
         debug!("with_generic_param_rib");
+        let LifetimeRibKind::Generics { binder, span: generics_span, kind: generics_kind, .. }
+            = lifetime_kind else { panic!() };
+
         let mut function_type_rib = Rib::new(kind);
         let mut function_value_rib = Rib::new(kind);
         let mut function_lifetime_rib = LifetimeRib::new(lifetime_kind);
@@ -1892,8 +1959,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                 GenericParamKind::Type { .. } => (&mut function_type_rib, DefKind::TyParam),
                 GenericParamKind::Const { .. } => (&mut function_value_rib, DefKind::ConstParam),
                 GenericParamKind::Lifetime => {
-                    let LifetimeRibKind::Generics { parent, .. } = lifetime_kind else { panic!() };
-                    let res = LifetimeRes::Param { param: def_id, binder: parent };
+                    let res = LifetimeRes::Param { param: def_id, binder };
                     self.record_lifetime_res(param.id, res);
                     function_lifetime_rib.bindings.insert(ident, (param.id, res));
                     continue;
@@ -1913,6 +1979,14 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
         self.ribs[TypeNS].pop();
         self.ribs[ValueNS].pop();
         self.lifetime_ribs.pop();
+
+        if let LifetimeBinderKind::BareFnType
+        | LifetimeBinderKind::WhereBound
+        | LifetimeBinderKind::Function
+        | LifetimeBinderKind::ImplBlock = generics_kind
+        {
+            self.maybe_report_lifetime_uses(generics_span, params)
+        }
     }
 
     fn with_label_rib(&mut self, kind: RibKind<'a>, f: impl FnOnce(&mut Self)) {
@@ -2039,7 +2113,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
     ) {
         debug!("resolve_implementation");
         // If applicable, create a rib for the type parameters.
-        self.with_generic_param_rib(&generics.params, ItemRibKind(HasGenericParams::Yes), LifetimeRibKind::Generics { span: generics.span, parent: item_id, kind: LifetimeBinderKind::ImplBlock }, |this| {
+        self.with_generic_param_rib(&generics.params, ItemRibKind(HasGenericParams::Yes), LifetimeRibKind::Generics { span: generics.span, binder: item_id, kind: LifetimeBinderKind::ImplBlock }, |this| {
             // Dummy self type for better errors if `Self` is used in the trait path.
             this.with_self_rib(Res::SelfTy { trait_: None, alias_to: None }, |this| {
                 this.with_lifetime_rib(LifetimeRibKind::AnonymousCreateParameter(item_id), |this| {
@@ -2066,7 +2140,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                             this.visit_generics(generics);
 
                             // Resolve the items within the impl.
-                            this.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(item_id),
+                            this.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(item_id,false),
                                 |this| {
                                     this.with_current_self_type(self_type, |this| {
                                         this.with_self_rib_ns(ValueNS, Res::SelfCtor(item_def_id), |this| {
@@ -2111,7 +2185,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                                                         this.with_generic_param_rib(
                                                             &generics.params,
                                                             AssocItemRibKind,
-                                                            LifetimeRibKind::Generics { parent: item.id, span: generics.span, kind: LifetimeBinderKind::Function },
+                                                            LifetimeRibKind::Generics { binder: item.id, span: generics.span, kind: LifetimeBinderKind::Function },
                                                             |this| {
                                                                 // If this is a trait impl, ensure the method
                                                                 // exists in trait
@@ -2140,7 +2214,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                                                         this.with_generic_param_rib(
                                                             &generics.params,
                                                             AssocItemRibKind,
-                                                            LifetimeRibKind::Generics { parent: item.id, span: generics.span, kind: LifetimeBinderKind::Item },
+                                                            LifetimeRibKind::Generics { binder: item.id, span: generics.span, kind: LifetimeBinderKind::Item },
                                                             |this| {
                                                                 // If this is a trait impl, ensure the type
                                                                 // exists in trait
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index 4f07d0076f1..318bb8280ec 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -1,16 +1,17 @@
 use crate::diagnostics::{ImportSuggestion, LabelSuggestion, TypoSuggestion};
 use crate::late::lifetimes::{ElisionFailureInfo, LifetimeContext};
 use crate::late::{AliasPossibility, LateResolutionVisitor, RibKind};
-use crate::late::{LifetimeBinderKind, LifetimeRibKind};
+use crate::late::{LifetimeBinderKind, LifetimeRibKind, LifetimeUseSet};
 use crate::path_names_to_string;
 use crate::{Module, ModuleKind, ModuleOrUniformRoot};
 use crate::{PathResult, PathSource, Segment};
 
-use rustc_ast::visit::{FnCtxt, FnKind};
+use rustc_ast::visit::{FnCtxt, FnKind, LifetimeCtxt};
 use rustc_ast::{
     self as ast, AssocItemKind, Expr, ExprKind, GenericParam, GenericParamKind, Item, ItemKind,
     NodeId, Path, Ty, TyKind,
 };
+use rustc_ast_lowering::ResolverAstLowering;
 use rustc_ast_pretty::pprust::path_segment_to_string;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{
@@ -22,6 +23,7 @@ use rustc_hir::def::Namespace::{self, *};
 use rustc_hir::def::{self, CtorKind, CtorOf, DefKind};
 use rustc_hir::def_id::{DefId, CRATE_DEF_ID, LOCAL_CRATE};
 use rustc_hir::PrimTy;
+use rustc_session::lint;
 use rustc_session::parse::feature_err;
 use rustc_span::edition::Edition;
 use rustc_span::hygiene::MacroKind;
@@ -1832,6 +1834,76 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
         })
     }
 
+    crate fn maybe_report_lifetime_uses(
+        &mut self,
+        generics_span: Span,
+        params: &[ast::GenericParam],
+    ) {
+        for (param_index, param) in params.iter().enumerate() {
+            let GenericParamKind::Lifetime = param.kind else { continue };
+
+            let def_id = self.r.local_def_id(param.id);
+
+            let use_set = self.lifetime_uses.remove(&def_id);
+            debug!(
+                "Use set for {:?}({:?} at {:?}) is {:?}",
+                def_id, param.ident, param.ident.span, use_set
+            );
+
+            let deletion_span = || {
+                if params.len() == 1 {
+                    // if sole lifetime, remove the entire `<>` brackets
+                    generics_span
+                } else if param_index == 0 {
+                    // if removing within `<>` brackets, we also want to
+                    // delete a leading or trailing comma as appropriate
+                    param.span().to(params[param_index + 1].span().shrink_to_lo())
+                } else {
+                    // if removing within `<>` brackets, we also want to
+                    // delete a leading or trailing comma as appropriate
+                    params[param_index - 1].span().shrink_to_hi().to(param.span())
+                }
+            };
+            match use_set {
+                Some(LifetimeUseSet::Many) => {}
+                Some(LifetimeUseSet::One { use_span, use_ctxt }) => {
+                    debug!(?param.ident, ?param.ident.span, ?use_span);
+
+                    let elidable = matches!(use_ctxt, LifetimeCtxt::Rptr);
+
+                    let deletion_span = deletion_span();
+                    self.r.lint_buffer.buffer_lint_with_diagnostic(
+                        lint::builtin::SINGLE_USE_LIFETIMES,
+                        param.id,
+                        param.ident.span,
+                        &format!("lifetime parameter `{}` only used once", param.ident),
+                        lint::BuiltinLintDiagnostics::SingleUseLifetime {
+                            param_span: param.ident.span,
+                            use_span: Some((use_span, elidable)),
+                            deletion_span,
+                        },
+                    );
+                }
+                None => {
+                    debug!(?param.ident, ?param.ident.span);
+
+                    let deletion_span = deletion_span();
+                    self.r.lint_buffer.buffer_lint_with_diagnostic(
+                        lint::builtin::UNUSED_LIFETIMES,
+                        param.id,
+                        param.ident.span,
+                        &format!("lifetime parameter `{}` never used", param.ident),
+                        lint::BuiltinLintDiagnostics::SingleUseLifetime {
+                            param_span: param.ident.span,
+                            use_span: None,
+                            deletion_span,
+                        },
+                    );
+                }
+            }
+        }
+    }
+
     crate fn emit_undeclared_lifetime_error(
         &self,
         lifetime_ref: &ast::Lifetime,
@@ -1863,7 +1935,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
 
         for rib in self.lifetime_ribs.iter().rev() {
             match rib.kind {
-                LifetimeRibKind::Generics { parent: _, span, kind } => {
+                LifetimeRibKind::Generics { binder: _, span, kind } => {
                     if !span.can_be_used_for_suggestions() && suggest_note {
                         suggest_note = false; // Avoid displaying the same help multiple times.
                         err.span_label(
diff --git a/compiler/rustc_resolve/src/late/lifetimes.rs b/compiler/rustc_resolve/src/late/lifetimes.rs
index 700d7c3bfb6..cacb851c4f0 100644
--- a/compiler/rustc_resolve/src/late/lifetimes.rs
+++ b/compiler/rustc_resolve/src/late/lifetimes.rs
@@ -9,20 +9,19 @@
 use crate::late::diagnostics::{ForLifetimeSpanType, MissingLifetimeSpot};
 use rustc_ast::walk_list;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
-use rustc_errors::{struct_span_err, Applicability, Diagnostic};
+use rustc_errors::struct_span_err;
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{DefIdMap, LocalDefId};
 use rustc_hir::hir_id::ItemLocalId;
 use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{GenericArg, GenericParam, LifetimeName, Node, ParamName, QPath};
+use rustc_hir::{GenericArg, GenericParam, LifetimeName, Node, ParamName};
 use rustc_hir::{GenericParamKind, HirIdMap, HirIdSet};
 use rustc_middle::hir::map::Map;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::middle::resolve_lifetime::*;
 use rustc_middle::ty::{self, DefIdTree, GenericParamDefKind, TyCtxt};
 use rustc_middle::{bug, span_bug};
-use rustc_session::lint;
 use rustc_span::def_id::DefId;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::Span;
@@ -33,13 +32,6 @@ use std::mem::take;
 
 use tracing::{debug, span, Level};
 
-// This counts the no of times a lifetime is used
-#[derive(Clone, Copy, Debug)]
-pub enum LifetimeUseSet<'tcx> {
-    One(&'tcx hir::Lifetime),
-    Many,
-}
-
 trait RegionExt {
     fn early(hir_map: Map<'_>, index: &mut u32, param: &GenericParam<'_>) -> (ParamName, Region);
 
@@ -175,8 +167,6 @@ crate struct LifetimeContext<'a, 'tcx> {
     /// Cache for cross-crate per-definition object lifetime defaults.
     xcrate_object_lifetime_defaults: DefIdMap<Vec<ObjectLifetimeDefault>>,
 
-    lifetime_uses: &'a mut DefIdMap<LifetimeUseSet<'tcx>>,
-
     /// When encountering an undefined named lifetime, we will suggest introducing it in these
     /// places.
     crate missing_named_lifetime_spots: Vec<MissingLifetimeSpot<'tcx>>,
@@ -197,11 +187,6 @@ enum Scope<'a> {
         /// we should use for an early-bound region?
         next_early_index: u32,
 
-        /// Flag is set to true if, in this binder, `'_` would be
-        /// equivalent to a "single-use region". This is true on
-        /// impls, but not other kinds of items.
-        track_lifetime_uses: bool,
-
         /// Whether or not this binder would serve as the parent
         /// binder for opaque types introduced within. For example:
         ///
@@ -297,7 +282,6 @@ impl<'a> fmt::Debug for TruncatedScopeDebug<'a> {
             Scope::Binder {
                 lifetimes,
                 next_early_index,
-                track_lifetime_uses,
                 opaque_type_parent,
                 scope_type,
                 hir_id,
@@ -307,7 +291,6 @@ impl<'a> fmt::Debug for TruncatedScopeDebug<'a> {
                 .debug_struct("Binder")
                 .field("lifetimes", lifetimes)
                 .field("next_early_index", next_early_index)
-                .field("track_lifetime_uses", track_lifetime_uses)
                 .field("opaque_type_parent", opaque_type_parent)
                 .field("scope_type", scope_type)
                 .field("hir_id", hir_id)
@@ -453,7 +436,6 @@ fn do_resolve(
         trait_definition_only,
         labels_in_fn: vec![],
         xcrate_object_lifetime_defaults: Default::default(),
-        lifetime_uses: &mut Default::default(),
         missing_named_lifetime_spots: vec![],
     };
     visitor.visit_item(item);
@@ -697,7 +679,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                     lifetimes: FxIndexMap::default(),
                     next_early_index: self.next_early_index(),
                     s: self.scope,
-                    track_lifetime_uses: true,
                     opaque_type_parent: false,
                     scope_type: BinderScopeType::Normal,
                     allow_late_bound: true,
@@ -796,9 +777,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
             | hir::ItemKind::Impl(hir::Impl { ref generics, .. }) => {
                 self.missing_named_lifetime_spots.push(generics.into());
 
-                // Impls permit `'_` to be used and it is equivalent to "some fresh lifetime name".
-                // This is not true for other kinds of items.
-                let track_lifetime_uses = matches!(item.kind, hir::ItemKind::Impl { .. });
                 // These kinds of items have only early-bound lifetime parameters.
                 let mut index = if sub_items_have_self_param(&item.kind) {
                     1 // Self comes before lifetimes
@@ -825,7 +803,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                     lifetimes,
                     next_early_index: index + non_lifetime_count,
                     opaque_type_parent: true,
-                    track_lifetime_uses,
                     scope_type: BinderScopeType::Normal,
                     s: ROOT_SCOPE,
                     allow_late_bound: false,
@@ -892,7 +869,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                     lifetimes,
                     s: self.scope,
                     next_early_index,
-                    track_lifetime_uses: true,
                     opaque_type_parent: false,
                     scope_type: BinderScopeType::Normal,
                     allow_late_bound: true,
@@ -1053,11 +1029,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                     match param.kind {
                         GenericParamKind::Lifetime { .. } => {
                             let (name, reg) = Region::early(self.tcx.hir(), &mut index, &param);
-                            let Region::EarlyBound(_, def_id) = reg else {
-                                bug!();
-                            };
-                            // We cannot predict what lifetimes are unused in opaque type.
-                            self.lifetime_uses.insert(def_id, LifetimeUseSet::Many);
                             if let hir::ParamName::Plain(Ident {
                                 name: kw::UnderscoreLifetime,
                                 ..
@@ -1087,7 +1058,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                             lifetimes,
                             next_early_index,
                             s: this.scope,
-                            track_lifetime_uses: true,
                             opaque_type_parent: false,
                             scope_type: BinderScopeType::Normal,
                             allow_late_bound: false,
@@ -1108,7 +1078,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                         lifetimes,
                         next_early_index,
                         s: self.scope,
-                        track_lifetime_uses: true,
                         opaque_type_parent: false,
                         scope_type: BinderScopeType::Normal,
                         allow_late_bound: false,
@@ -1168,7 +1137,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                     lifetimes,
                     next_early_index: index + non_lifetime_count,
                     s: self.scope,
-                    track_lifetime_uses: true,
                     opaque_type_parent: true,
                     scope_type: BinderScopeType::Normal,
                     allow_late_bound: false,
@@ -1238,7 +1206,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                     lifetimes,
                     next_early_index: index + non_lifetime_count,
                     s: self.scope,
-                    track_lifetime_uses: true,
                     opaque_type_parent: true,
                     scope_type: BinderScopeType::Normal,
                     allow_late_bound: true,
@@ -1383,7 +1350,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                             lifetimes,
                             s: this.scope,
                             next_early_index,
-                            track_lifetime_uses: true,
                             opaque_type_parent: false,
                             scope_type: BinderScopeType::Normal,
                             allow_late_bound: true,
@@ -1457,7 +1423,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
                     lifetimes: FxIndexMap::default(),
                     s: self.scope,
                     next_early_index: self.next_early_index(),
-                    track_lifetime_uses: true,
                     opaque_type_parent: false,
                     scope_type,
                     allow_late_bound: true,
@@ -1510,7 +1475,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
             lifetimes,
             s: self.scope,
             next_early_index,
-            track_lifetime_uses: true,
             opaque_type_parent: false,
             scope_type,
             allow_late_bound: true,
@@ -1812,7 +1776,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
     where
         F: for<'b> FnOnce(ScopeRef<'_>, &mut LifetimeContext<'b, 'tcx>),
     {
-        let LifetimeContext { tcx, map, lifetime_uses, .. } = self;
+        let LifetimeContext { tcx, map, .. } = self;
         let labels_in_fn = take(&mut self.labels_in_fn);
         let xcrate_object_lifetime_defaults = take(&mut self.xcrate_object_lifetime_defaults);
         let missing_named_lifetime_spots = take(&mut self.missing_named_lifetime_spots);
@@ -1823,298 +1787,18 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
             trait_definition_only: self.trait_definition_only,
             labels_in_fn,
             xcrate_object_lifetime_defaults,
-            lifetime_uses,
             missing_named_lifetime_spots,
         };
         let span = tracing::debug_span!("scope", scope = ?TruncatedScopeDebug(&this.scope));
         {
             let _enter = span.enter();
             f(self.scope, &mut this);
-            if !self.trait_definition_only {
-                this.check_uses_for_lifetimes_defined_by_scope();
-            }
         }
         self.labels_in_fn = this.labels_in_fn;
         self.xcrate_object_lifetime_defaults = this.xcrate_object_lifetime_defaults;
         self.missing_named_lifetime_spots = this.missing_named_lifetime_spots;
     }
 
-    /// helper method to determine the span to remove when suggesting the
-    /// deletion of a lifetime
-    fn lifetime_deletion_span(&self, name: Ident, generics: &hir::Generics<'_>) -> Option<Span> {
-        generics.params.iter().enumerate().find_map(|(i, param)| {
-            if param.name.ident() == name {
-                if generics.params.len() == 1 {
-                    // if sole lifetime, remove the entire `<>` brackets
-                    Some(generics.span)
-                } else {
-                    // if removing within `<>` brackets, we also want to
-                    // delete a leading or trailing comma as appropriate
-                    if i >= generics.params.len() - 1 {
-                        Some(generics.params[i - 1].span.shrink_to_hi().to(param.span))
-                    } else {
-                        Some(param.span.to(generics.params[i + 1].span.shrink_to_lo()))
-                    }
-                }
-            } else {
-                None
-            }
-        })
-    }
-
-    // helper method to issue suggestions from `fn rah<'a>(&'a T)` to `fn rah(&T)`
-    // or from `fn rah<'a>(T<'a>)` to `fn rah(T<'_>)`
-    fn suggest_eliding_single_use_lifetime(
-        &self,
-        err: &mut Diagnostic,
-        def_id: DefId,
-        lifetime: &hir::Lifetime,
-    ) {
-        let name = lifetime.name.ident();
-        let remove_decl = self
-            .tcx
-            .parent(def_id)
-            .as_local()
-            .and_then(|parent_def_id| self.tcx.hir().get_generics(parent_def_id))
-            .and_then(|generics| self.lifetime_deletion_span(name, generics));
-
-        let mut remove_use = None;
-        let mut elide_use = None;
-        let mut find_arg_use_span = |inputs: &[hir::Ty<'_>]| {
-            for input in inputs {
-                match input.kind {
-                    hir::TyKind::Rptr(lt, _) => {
-                        if lt.name.ident() == name {
-                            // include the trailing whitespace between the lifetime and type names
-                            let lt_through_ty_span = lifetime.span.to(input.span.shrink_to_hi());
-                            remove_use = Some(
-                                self.tcx
-                                    .sess
-                                    .source_map()
-                                    .span_until_non_whitespace(lt_through_ty_span),
-                            );
-                            break;
-                        }
-                    }
-                    hir::TyKind::Path(QPath::Resolved(_, path)) => {
-                        let last_segment = &path.segments[path.segments.len() - 1];
-                        let generics = last_segment.args();
-                        for arg in generics.args.iter() {
-                            if let GenericArg::Lifetime(lt) = arg {
-                                if lt.name.ident() == name {
-                                    elide_use = Some(lt.span);
-                                    break;
-                                }
-                            }
-                        }
-                        break;
-                    }
-                    _ => {}
-                }
-            }
-        };
-        if let Node::Lifetime(hir_lifetime) = self.tcx.hir().get(lifetime.hir_id) {
-            if let Some(parent) =
-                self.tcx.hir().find_by_def_id(self.tcx.hir().get_parent_item(hir_lifetime.hir_id))
-            {
-                match parent {
-                    Node::Item(item) => {
-                        if let hir::ItemKind::Fn(sig, _, _) = &item.kind {
-                            find_arg_use_span(sig.decl.inputs);
-                        }
-                    }
-                    Node::ImplItem(impl_item) => {
-                        if let hir::ImplItemKind::Fn(sig, _) = &impl_item.kind {
-                            find_arg_use_span(sig.decl.inputs);
-                        }
-                    }
-                    _ => {}
-                }
-            }
-        }
-
-        let msg = "elide the single-use lifetime";
-        match (remove_decl, remove_use, elide_use) {
-            (Some(decl_span), Some(use_span), None) => {
-                // if both declaration and use deletion spans start at the same
-                // place ("start at" because the latter includes trailing
-                // whitespace), then this is an in-band lifetime
-                if decl_span.shrink_to_lo() == use_span.shrink_to_lo() {
-                    err.span_suggestion(
-                        use_span,
-                        msg,
-                        String::new(),
-                        Applicability::MachineApplicable,
-                    );
-                } else {
-                    err.multipart_suggestion(
-                        msg,
-                        vec![(decl_span, String::new()), (use_span, String::new())],
-                        Applicability::MachineApplicable,
-                    );
-                }
-            }
-            (Some(decl_span), None, Some(use_span)) => {
-                err.multipart_suggestion(
-                    msg,
-                    vec![(decl_span, String::new()), (use_span, "'_".to_owned())],
-                    Applicability::MachineApplicable,
-                );
-            }
-            _ => {}
-        }
-    }
-
-    fn check_uses_for_lifetimes_defined_by_scope(&mut self) {
-        let Scope::Binder { lifetimes: defined_by, .. } = self.scope else {
-            debug!("check_uses_for_lifetimes_defined_by_scope: not in a binder scope");
-            return;
-        };
-
-        let def_ids: Vec<_> = defined_by
-            .values()
-            .flat_map(|region| match region {
-                Region::EarlyBound(_, def_id)
-                | Region::LateBound(_, _, def_id)
-                | Region::Free(_, def_id) => Some(*def_id),
-
-                Region::LateBoundAnon(..) | Region::Static => None,
-            })
-            .collect();
-
-        'lifetimes: for def_id in def_ids {
-            debug!("check_uses_for_lifetimes_defined_by_scope: def_id = {:?}", def_id);
-
-            let lifetimeuseset = self.lifetime_uses.remove(&def_id);
-
-            debug!(
-                "check_uses_for_lifetimes_defined_by_scope: lifetimeuseset = {:?}",
-                lifetimeuseset
-            );
-
-            match lifetimeuseset {
-                Some(LifetimeUseSet::One(lifetime)) => {
-                    debug!(?def_id);
-                    if let Some((id, span, name)) =
-                        match self.tcx.hir().get_by_def_id(def_id.expect_local()) {
-                            Node::Lifetime(hir_lifetime) => Some((
-                                hir_lifetime.hir_id,
-                                hir_lifetime.span,
-                                hir_lifetime.name.ident(),
-                            )),
-                            Node::GenericParam(param) => {
-                                Some((param.hir_id, param.span, param.name.ident()))
-                            }
-                            _ => None,
-                        }
-                    {
-                        debug!("id = {:?} span = {:?} name = {:?}", id, span, name);
-                        if name.name == kw::UnderscoreLifetime {
-                            continue;
-                        }
-
-                        let parent_def_id = self.tcx.parent(def_id);
-                        if let Some(def_id) = parent_def_id.as_local() {
-                            // lifetimes in `derive` expansions don't count (Issue #53738)
-                            if self.tcx.has_attr(def_id.to_def_id(), sym::automatically_derived) {
-                                continue;
-                            }
-
-                            // opaque types generated when desugaring an async function can have a single
-                            // use lifetime even if it is explicitly denied (Issue #77175)
-                            if let hir::Node::Item(hir::Item {
-                                kind: hir::ItemKind::OpaqueTy(ref opaque),
-                                ..
-                            }) = self.tcx.hir().get_by_def_id(def_id)
-                            {
-                                if !matches!(opaque.origin, hir::OpaqueTyOrigin::AsyncFn(..)) {
-                                    continue 'lifetimes;
-                                }
-                                // We want to do this only if the lifetime identifier is already defined
-                                // in the async function that generated this. Otherwise it could be
-                                // an opaque type defined by the developer and we still want this
-                                // lint to fail compilation
-                                for p in opaque.generics.params {
-                                    if defined_by.contains_key(&p.name) {
-                                        continue 'lifetimes;
-                                    }
-                                }
-                            }
-                        }
-
-                        self.tcx.struct_span_lint_hir(
-                            lint::builtin::SINGLE_USE_LIFETIMES,
-                            id,
-                            span,
-                            |lint| {
-                                let mut err = lint.build(&format!(
-                                    "lifetime parameter `{}` only used once",
-                                    name
-                                ));
-                                if span == lifetime.span {
-                                    // spans are the same for in-band lifetime declarations
-                                    err.span_label(span, "this lifetime is only used here");
-                                } else {
-                                    err.span_label(span, "this lifetime...");
-                                    err.span_label(lifetime.span, "...is used only here");
-                                }
-                                self.suggest_eliding_single_use_lifetime(
-                                    &mut err, def_id, lifetime,
-                                );
-                                err.emit();
-                            },
-                        );
-                    }
-                }
-                Some(LifetimeUseSet::Many) => {
-                    debug!("not one use lifetime");
-                }
-                None => {
-                    if let Some((id, span, name)) =
-                        match self.tcx.hir().get_by_def_id(def_id.expect_local()) {
-                            Node::Lifetime(hir_lifetime) => Some((
-                                hir_lifetime.hir_id,
-                                hir_lifetime.span,
-                                hir_lifetime.name.ident(),
-                            )),
-                            Node::GenericParam(param) => {
-                                Some((param.hir_id, param.span, param.name.ident()))
-                            }
-                            _ => None,
-                        }
-                    {
-                        debug!("id ={:?} span = {:?} name = {:?}", id, span, name);
-                        self.tcx.struct_span_lint_hir(
-                            lint::builtin::UNUSED_LIFETIMES,
-                            id,
-                            span,
-                            |lint| {
-                                let mut err = lint
-                                    .build(&format!("lifetime parameter `{}` never used", name));
-                                let parent_def_id = self.tcx.parent(def_id);
-                                if let Some(generics) =
-                                    self.tcx.hir().get_generics(parent_def_id.expect_local())
-                                {
-                                    let unused_lt_span =
-                                        self.lifetime_deletion_span(name, generics);
-                                    if let Some(span) = unused_lt_span {
-                                        err.span_suggestion(
-                                            span,
-                                            "elide the unused lifetime",
-                                            String::new(),
-                                            Applicability::MachineApplicable,
-                                        );
-                                    }
-                                }
-                                err.emit();
-                            },
-                        );
-                    }
-                }
-            }
-        }
-    }
-
     /// Visits self by adding a scope and handling recursive walk over the contents with `walk`.
     ///
     /// Handles visiting fns and methods. These are a bit complicated because we must distinguish
@@ -2204,7 +1888,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
             next_early_index,
             s: self.scope,
             opaque_type_parent: true,
-            track_lifetime_uses: false,
             scope_type: BinderScopeType::Normal,
             allow_late_bound: true,
         };
@@ -3201,41 +2884,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
         }
     }
 
-    /// Returns `true` if, in the current scope, replacing `'_` would be
-    /// equivalent to a single-use lifetime.
-    fn track_lifetime_uses(&self) -> bool {
-        let mut scope = self.scope;
-        loop {
-            match *scope {
-                Scope::Root => break false,
-
-                // Inside of items, it depends on the kind of item.
-                Scope::Binder { track_lifetime_uses, .. } => break track_lifetime_uses,
-
-                // Inside a body, `'_` will use an inference variable,
-                // should be fine.
-                Scope::Body { .. } => break true,
-
-                // A lifetime only used in a fn argument could as well
-                // be replaced with `'_`, as that would generate a
-                // fresh name, too.
-                Scope::Elision { elide: Elide::FreshLateAnon(..), .. } => break true,
-
-                // In the return type or other such place, `'_` is not
-                // going to make a fresh name, so we cannot
-                // necessarily replace a single-use lifetime with
-                // `'_`.
-                Scope::Elision {
-                    elide: Elide::Exact(_) | Elide::Error(_) | Elide::Forbid, ..
-                } => break false,
-
-                Scope::ObjectLifetimeDefault { s, .. }
-                | Scope::Supertrait { s, .. }
-                | Scope::TraitRefBoundary { s, .. } => scope = s,
-            }
-        }
-    }
-
     #[tracing::instrument(level = "debug", skip(self))]
     fn insert_lifetime(&mut self, lifetime_ref: &'tcx hir::Lifetime, def: Region) {
         debug!(
@@ -3243,27 +2891,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
             span = ?self.tcx.sess.source_map().span_to_diagnostic_string(lifetime_ref.span)
         );
         self.map.defs.insert(lifetime_ref.hir_id, def);
-
-        match def {
-            Region::LateBoundAnon(..) | Region::Static => {
-                // These are anonymous lifetimes or lifetimes that are not declared.
-            }
-
-            Region::Free(_, def_id)
-            | Region::LateBound(_, _, def_id)
-            | Region::EarlyBound(_, def_id) => {
-                // A lifetime declared by the user.
-                let track_lifetime_uses = self.track_lifetime_uses();
-                debug!(?track_lifetime_uses);
-                if track_lifetime_uses && !self.lifetime_uses.contains_key(&def_id) {
-                    debug!("first use of {:?}", def_id);
-                    self.lifetime_uses.insert(def_id, LifetimeUseSet::One(lifetime_ref));
-                } else {
-                    debug!("many uses of {:?}", def_id);
-                    self.lifetime_uses.insert(def_id, LifetimeUseSet::Many);
-                }
-            }
-        }
     }
 
     /// Sometimes we resolve a lifetime, but later find that it is an
diff --git a/src/test/ui/async-await/unused-lifetime.rs b/src/test/ui/async-await/unused-lifetime.rs
index 5bd6ae8d3a4..6cfd36ba9e8 100644
--- a/src/test/ui/async-await/unused-lifetime.rs
+++ b/src/test/ui/async-await/unused-lifetime.rs
@@ -1,19 +1,15 @@
 // Check "unused_lifetimes" lint on both async and sync functions
+// Both cases should be diagnosed the same way.
 
 // edition:2018
 
 #![deny(unused_lifetimes)]
 
+async fn async_wrong_without_args<'a>() {} //~ ERROR lifetime parameter `'a` never used
 
-// Async part with unused lifetimes
-//
-// Even wrong cases don't cause errors because async functions are desugared with all lifetimes
-// involved in the signature. So, we cannot predict what lifetimes are unused in async function.
-async fn async_wrong_without_args<'a>() {}
+async fn async_wrong_1_lifetime<'a>(_: &i32) {} //~ ERROR lifetime parameter `'a` never used
 
-async fn async_wrong_1_lifetime<'a>(_: &i32) {}
-
-async fn async_wrong_2_lifetimes<'a, 'b>(_: &'a i32, _: &i32) {}
+async fn async_wrong_2_lifetimes<'a, 'b>(_: &'a i32, _: &i32) {} //~ ERROR lifetime parameter `'b` never used
 
 async fn async_right_1_lifetime<'a>(_: &'a i32) {}
 
@@ -24,10 +20,6 @@ where
     I: Iterator<Item = &'a i32>
 {}
 
-
-// Sync part with unused lifetimes
-//
-// These functions are compiled as supposed
 fn wrong_without_args<'a>() {} //~ ERROR lifetime parameter `'a` never used
 
 fn wrong_1_lifetime<'a>(_: &i32) {} //~ ERROR lifetime parameter `'a` never used
diff --git a/src/test/ui/async-await/unused-lifetime.stderr b/src/test/ui/async-await/unused-lifetime.stderr
index 4e90f43fdd0..5c00501a62f 100644
--- a/src/test/ui/async-await/unused-lifetime.stderr
+++ b/src/test/ui/async-await/unused-lifetime.stderr
@@ -1,28 +1,48 @@
 error: lifetime parameter `'a` never used
-  --> $DIR/unused-lifetime.rs:31:23
+  --> $DIR/unused-lifetime.rs:8:35
    |
-LL | fn wrong_without_args<'a>() {}
-   |                      -^^- help: elide the unused lifetime
+LL | async fn async_wrong_without_args<'a>() {}
+   |                                  -^^- help: elide the unused lifetime
    |
 note: the lint level is defined here
-  --> $DIR/unused-lifetime.rs:5:9
+  --> $DIR/unused-lifetime.rs:6:9
    |
 LL | #![deny(unused_lifetimes)]
    |         ^^^^^^^^^^^^^^^^
 
 error: lifetime parameter `'a` never used
-  --> $DIR/unused-lifetime.rs:33:21
+  --> $DIR/unused-lifetime.rs:10:33
+   |
+LL | async fn async_wrong_1_lifetime<'a>(_: &i32) {}
+   |                                -^^- help: elide the unused lifetime
+
+error: lifetime parameter `'b` never used
+  --> $DIR/unused-lifetime.rs:12:38
+   |
+LL | async fn async_wrong_2_lifetimes<'a, 'b>(_: &'a i32, _: &i32) {}
+   |                                    --^^
+   |                                    |
+   |                                    help: elide the unused lifetime
+
+error: lifetime parameter `'a` never used
+  --> $DIR/unused-lifetime.rs:23:23
+   |
+LL | fn wrong_without_args<'a>() {}
+   |                      -^^- help: elide the unused lifetime
+
+error: lifetime parameter `'a` never used
+  --> $DIR/unused-lifetime.rs:25:21
    |
 LL | fn wrong_1_lifetime<'a>(_: &i32) {}
    |                    -^^- help: elide the unused lifetime
 
 error: lifetime parameter `'b` never used
-  --> $DIR/unused-lifetime.rs:35:26
+  --> $DIR/unused-lifetime.rs:27:26
    |
 LL | fn wrong_2_lifetimes<'a, 'b>(_: &'a i32, _: &i32) {}
    |                        --^^
    |                        |
    |                        help: elide the unused lifetime
 
-error: aborting due to 3 previous errors
+error: aborting due to 6 previous errors
 
diff --git a/src/test/ui/single-use-lifetime/fn-types.stderr b/src/test/ui/single-use-lifetime/fn-types.stderr
index 584c889506b..9290c21620e 100644
--- a/src/test/ui/single-use-lifetime/fn-types.stderr
+++ b/src/test/ui/single-use-lifetime/fn-types.stderr
@@ -11,6 +11,11 @@ note: the lint level is defined here
    |
 LL | #![deny(single_use_lifetimes)]
    |         ^^^^^^^^^^^^^^^^^^^^
+help: elide the single-use lifetime
+   |
+LL -   a: for<'a> fn(&'a u32),
+LL +   a: fn(&u32),
+   | 
 
 error[E0581]: return type references lifetime `'a`, which is not constrained by the fn input types
   --> $DIR/fn-types.rs:12:22
diff --git a/src/test/ui/single-use-lifetime/one-use-in-fn-argument.rs b/src/test/ui/single-use-lifetime/one-use-in-fn-argument.rs
index ff9d6bd01c6..7919ef820f6 100644
--- a/src/test/ui/single-use-lifetime/one-use-in-fn-argument.rs
+++ b/src/test/ui/single-use-lifetime/one-use-in-fn-argument.rs
@@ -19,4 +19,15 @@ fn left<'x, 'y>(foo: Double<'x, 'y>) -> &'x u32 { foo.f } //~ ERROR `'y` only us
 fn right<'x, 'y>(foo: Double<'x, 'y>) -> &'y u32 { foo.f } //~ ERROR `'x` only used once
 //~^ HELP elide the single-use lifetime
 
-fn main() { }
+pub trait Tfv<'a> {}
+
+// Do NOT lint in an HRTB.
+pub fn g<T: for<'a> Tfv<'a>>() {}
+
+// Do NOT lint for trait bounds.
+pub fn h<'a, S>(_: S)
+where
+    S: Tfv<'a>,
+{}
+
+fn main() {}
diff --git a/src/test/ui/single-use-lifetime/one-use-in-fn-return.rs b/src/test/ui/single-use-lifetime/one-use-in-fn-return.rs
index 7b7ff08da7c..1ade01eed36 100644
--- a/src/test/ui/single-use-lifetime/one-use-in-fn-return.rs
+++ b/src/test/ui/single-use-lifetime/one-use-in-fn-return.rs
@@ -14,4 +14,10 @@ fn b<'a>() -> &'a u32 {
     &22
 }
 
+pub trait Tfv<'a> {}
+impl Tfv<'_> for () {}
+
+// Do NOT lint if used in return type.
+pub fn i<'a>() -> impl Tfv<'a> {}
+
 fn main() {}
diff --git a/src/test/ui/single-use-lifetime/one-use-in-inherent-impl-header.stderr b/src/test/ui/single-use-lifetime/one-use-in-inherent-impl-header.stderr
index 35fd782e133..cf34a1ca299 100644
--- a/src/test/ui/single-use-lifetime/one-use-in-inherent-impl-header.stderr
+++ b/src/test/ui/single-use-lifetime/one-use-in-inherent-impl-header.stderr
@@ -11,6 +11,11 @@ note: the lint level is defined here
    |
 LL | #![deny(single_use_lifetimes)]
    |         ^^^^^^^^^^^^^^^^^^^^
+help: elide the single-use lifetime
+   |
+LL - impl<'f> Foo<'f> {
+LL + impl Foo<'_> {
+   | 
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.rs b/src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.rs
index e7bdbb2207a..eecd715efc1 100644
--- a/src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.rs
+++ b/src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.rs
@@ -9,6 +9,7 @@ struct Foo<'f> {
 }
 
 impl<'f> Foo<'f> { //~ ERROR `'f` only used once
+    //~^ HELP elide the single-use lifetime
     fn inherent_a<'a>(&self, data: &'a u32) { //~ ERROR `'a` only used once
         //~^ HELP elide the single-use lifetime
     }
diff --git a/src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.stderr b/src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.stderr
index b8b78cd87b0..846c1bf41a2 100644
--- a/src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.stderr
+++ b/src/test/ui/single-use-lifetime/one-use-in-inherent-method-argument.stderr
@@ -1,10 +1,10 @@
-error: lifetime parameter `'a` only used once
-  --> $DIR/one-use-in-inherent-method-argument.rs:12:19
+error: lifetime parameter `'f` only used once
+  --> $DIR/one-use-in-inherent-method-argument.rs:11:6
    |
-LL |     fn inherent_a<'a>(&self, data: &'a u32) {
-   |                   ^^                -- ...is used only here
-   |                   |
-   |                   this lifetime...
+LL | impl<'f> Foo<'f> {
+   |      ^^      -- ...is used only here
+   |      |
+   |      this lifetime...
    |
 note: the lint level is defined here
   --> $DIR/one-use-in-inherent-method-argument.rs:1:9
@@ -13,17 +13,23 @@ LL | #![deny(single_use_lifetimes)]
    |         ^^^^^^^^^^^^^^^^^^^^
 help: elide the single-use lifetime
    |
-LL -     fn inherent_a<'a>(&self, data: &'a u32) {
-LL +     fn inherent_a(&self, data: &u32) {
+LL - impl<'f> Foo<'f> {
+LL + impl Foo<'_> {
    | 
 
-error: lifetime parameter `'f` only used once
-  --> $DIR/one-use-in-inherent-method-argument.rs:11:6
+error: lifetime parameter `'a` only used once
+  --> $DIR/one-use-in-inherent-method-argument.rs:13:19
    |
-LL | impl<'f> Foo<'f> {
-   |      ^^      -- ...is used only here
-   |      |
-   |      this lifetime...
+LL |     fn inherent_a<'a>(&self, data: &'a u32) {
+   |                   ^^                -- ...is used only here
+   |                   |
+   |                   this lifetime...
+   |
+help: elide the single-use lifetime
+   |
+LL -     fn inherent_a<'a>(&self, data: &'a u32) {
+LL +     fn inherent_a(&self, data: &u32) {
+   | 
 
 error: aborting due to 2 previous errors
 
diff --git a/src/test/ui/single-use-lifetime/one-use-in-inherent-method-return.stderr b/src/test/ui/single-use-lifetime/one-use-in-inherent-method-return.stderr
index da9e2534611..790fcaa409c 100644
--- a/src/test/ui/single-use-lifetime/one-use-in-inherent-method-return.stderr
+++ b/src/test/ui/single-use-lifetime/one-use-in-inherent-method-return.stderr
@@ -11,6 +11,11 @@ note: the lint level is defined here
    |
 LL | #![deny(single_use_lifetimes)]
    |         ^^^^^^^^^^^^^^^^^^^^
+help: elide the single-use lifetime
+   |
+LL - impl<'f> Foo<'f> {
+LL + impl Foo<'_> {
+   | 
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/single-use-lifetime/one-use-in-struct.rs b/src/test/ui/single-use-lifetime/one-use-in-struct.rs
index 9082aa68ed2..9cad942e7a2 100644
--- a/src/test/ui/single-use-lifetime/one-use-in-struct.rs
+++ b/src/test/ui/single-use-lifetime/one-use-in-struct.rs
@@ -4,7 +4,8 @@
 //
 // check-pass
 
-#![deny(single_use_lifetimes)]
+// Use forbid to verify that `automatically_derived` is handled correctly.
+#![forbid(single_use_lifetimes)]
 #![allow(dead_code)]
 #![allow(unused_variables)]
 
diff --git a/src/test/ui/single-use-lifetime/one-use-in-trait-method-argument.rs b/src/test/ui/single-use-lifetime/one-use-in-trait-method-argument.rs
index 6a66c17538a..1848fc91c62 100644
--- a/src/test/ui/single-use-lifetime/one-use-in-trait-method-argument.rs
+++ b/src/test/ui/single-use-lifetime/one-use-in-trait-method-argument.rs
@@ -18,4 +18,9 @@ impl<'f> Iterator for Foo<'f> {
     }
 }
 
-fn main() { }
+trait Bar<'a> {
+    // But we should not warn here.
+    fn bar(x: Foo<'a>);
+}
+
+fn main() {}
diff --git a/src/test/ui/single-use-lifetime/two-uses-in-inherent-method-argument-and-return.stderr b/src/test/ui/single-use-lifetime/two-uses-in-inherent-method-argument-and-return.stderr
index c16b244fafd..b50975a189e 100644
--- a/src/test/ui/single-use-lifetime/two-uses-in-inherent-method-argument-and-return.stderr
+++ b/src/test/ui/single-use-lifetime/two-uses-in-inherent-method-argument-and-return.stderr
@@ -11,6 +11,11 @@ note: the lint level is defined here
    |
 LL | #![deny(single_use_lifetimes)]
    |         ^^^^^^^^^^^^^^^^^^^^
+help: elide the single-use lifetime
+   |
+LL - impl<'f> Foo<'f> {
+LL + impl Foo<'_> {
+   | 
 
 error: aborting due to previous error