about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2024-04-10 16:15:22 +0200
committerGitHub <noreply@github.com>2024-04-10 16:15:22 +0200
commitfa696a3629f69e79780d36810085a2f3436808a7 (patch)
treef42b93959ee1c27fa6e4a6de9bd21c6408c40356
parentb14d8b2ef20c64c1002e2c6c724025c3d0846b91 (diff)
parentda2b714ba122d7befe1f10c808e82a536c9efcf0 (diff)
downloadrust-fa696a3629f69e79780d36810085a2f3436808a7.tar.gz
rust-fa696a3629f69e79780d36810085a2f3436808a7.zip
Rollup merge of #118391 - compiler-errors:lifetimes-eq, r=lcnr
Add `REDUNDANT_LIFETIMES` lint to detect lifetimes which are semantically redundant

There already is a `UNUSED_LIFETIMES` lint which is fired when we detect where clause bounds like `where 'a: 'static`, however, it doesn't use the full power of lexical region resolution to detect failures.

Right now `UNUSED_LIFETIMES` is an `Allow` lint, though presumably we could bump it to warn? I can (somewhat) easily implement a structured suggestion so this can be rustfix'd automatically, since we can just walk through the HIR body, replacing instances of the redundant lifetime.

Fixes #118376
r? lcnr
-rw-r--r--compiler/rustc_errors/src/diagnostic_impls.rs1
-rw-r--r--compiler/rustc_hir_analysis/messages.ftl3
-rw-r--r--compiler/rustc_hir_analysis/src/check/wfcheck.rs135
-rw-r--r--compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs26
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs28
-rw-r--r--compiler/rustc_middle/src/ty/context.rs2
-rw-r--r--compiler/rustc_middle/src/ty/diagnostics.rs15
-rw-r--r--tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.rs5
-rw-r--r--tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.stderr42
-rw-r--r--tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.rs6
-rw-r--r--tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.stderr12
-rw-r--r--tests/ui/regions/regions-static-bound-rpass.rs9
-rw-r--r--tests/ui/regions/regions-static-bound-rpass.stderr38
-rw-r--r--tests/ui/regions/regions-static-bound.rs9
-rw-r--r--tests/ui/regions/regions-static-bound.stderr37
-rw-r--r--tests/ui/regions/transitively-redundant-lifetimes.rs20
-rw-r--r--tests/ui/regions/transitively-redundant-lifetimes.stderr47
17 files changed, 332 insertions, 103 deletions
diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs
index f90190797ae..6c0551848d6 100644
--- a/compiler/rustc_errors/src/diagnostic_impls.rs
+++ b/compiler/rustc_errors/src/diagnostic_impls.rs
@@ -46,6 +46,7 @@ impl<'a, T: Clone + IntoDiagArg> IntoDiagArg for &'a T {
     }
 }
 
+#[macro_export]
 macro_rules! into_diag_arg_using_display {
     ($( $ty:ty ),+ $(,)?) => {
         $(
diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl
index e66a834ab9e..86b8b6d6b2b 100644
--- a/compiler/rustc_hir_analysis/messages.ftl
+++ b/compiler/rustc_hir_analysis/messages.ftl
@@ -355,6 +355,9 @@ hir_analysis_pattern_type_wild_pat = "wildcard patterns are not permitted for pa
 hir_analysis_placeholder_not_allowed_item_signatures = the placeholder `_` is not allowed within types on item signatures for {$kind}
     .label = not allowed in type signatures
 
+hir_analysis_redundant_lifetime_args = unnecessary lifetime parameter `{$victim}`
+    .note = you can use the `{$candidate}` lifetime directly, in place of `{$victim}`
+
 hir_analysis_requires_note = the `{$trait_name}` impl for `{$ty}` requires that `{$error_predicate}`
 
 hir_analysis_return_type_notation_equality_bound =
diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs
index 4fd7c870fc7..c26f982fa47 100644
--- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs
+++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs
@@ -8,11 +8,13 @@ use rustc_ast as ast;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
 use rustc_errors::{codes::*, pluralize, struct_span_code_err, Applicability, ErrorGuaranteed};
 use rustc_hir as hir;
+use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
 use rustc_hir::lang_items::LangItem;
 use rustc_hir::ItemKind;
 use rustc_infer::infer::outlives::env::OutlivesEnvironment;
 use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt};
+use rustc_macros::LintDiagnostic;
 use rustc_middle::query::Providers;
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::trait_def::TraitSpecializationKind;
@@ -136,6 +138,8 @@ where
         infcx.implied_bounds_tys_compat(param_env, body_def_id, &assumed_wf_types, false);
     let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds);
 
+    lint_redundant_lifetimes(tcx, body_def_id, &outlives_env);
+
     let errors = infcx.resolve_regions(&outlives_env);
     if errors.is_empty() {
         return Ok(());
@@ -2010,6 +2014,137 @@ fn check_mod_type_wf(tcx: TyCtxt<'_>, module: LocalModDefId) -> Result<(), Error
     res
 }
 
+fn lint_redundant_lifetimes<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    owner_id: LocalDefId,
+    outlives_env: &OutlivesEnvironment<'tcx>,
+) {
+    let def_kind = tcx.def_kind(owner_id);
+    match def_kind {
+        DefKind::Struct
+        | DefKind::Union
+        | DefKind::Enum
+        | DefKind::Trait
+        | DefKind::TraitAlias
+        | DefKind::Fn
+        | DefKind::Const
+        | DefKind::Impl { of_trait: _ } => {
+            // Proceed
+        }
+        DefKind::AssocFn | DefKind::AssocTy | DefKind::AssocConst => {
+            let parent_def_id = tcx.local_parent(owner_id);
+            if matches!(tcx.def_kind(parent_def_id), DefKind::Impl { of_trait: true }) {
+                // Don't check for redundant lifetimes for associated items of trait
+                // implementations, since the signature is required to be compatible
+                // with the trait, even if the implementation implies some lifetimes
+                // are redundant.
+                return;
+            }
+        }
+        DefKind::Mod
+        | DefKind::Variant
+        | DefKind::TyAlias
+        | DefKind::ForeignTy
+        | DefKind::TyParam
+        | DefKind::ConstParam
+        | DefKind::Static { .. }
+        | DefKind::Ctor(_, _)
+        | DefKind::Macro(_)
+        | DefKind::ExternCrate
+        | DefKind::Use
+        | DefKind::ForeignMod
+        | DefKind::AnonConst
+        | DefKind::InlineConst
+        | DefKind::OpaqueTy
+        | DefKind::Field
+        | DefKind::LifetimeParam
+        | DefKind::GlobalAsm
+        | DefKind::Closure => return,
+    }
+
+    // The ordering of this lifetime map is a bit subtle.
+    //
+    // Specifically, we want to find a "candidate" lifetime that precedes a "victim" lifetime,
+    // where we can prove that `'candidate = 'victim`.
+    //
+    // `'static` must come first in this list because we can never replace `'static` with
+    // something else, but if we find some lifetime `'a` where `'a = 'static`, we want to
+    // suggest replacing `'a` with `'static`.
+    let mut lifetimes = vec![tcx.lifetimes.re_static];
+    lifetimes.extend(
+        ty::GenericArgs::identity_for_item(tcx, owner_id).iter().filter_map(|arg| arg.as_region()),
+    );
+    // If we are in a function, add its late-bound lifetimes too.
+    if matches!(def_kind, DefKind::Fn | DefKind::AssocFn) {
+        for var in tcx.fn_sig(owner_id).instantiate_identity().bound_vars() {
+            let ty::BoundVariableKind::Region(kind) = var else { continue };
+            lifetimes.push(ty::Region::new_late_param(tcx, owner_id.to_def_id(), kind));
+        }
+    }
+    lifetimes.retain(|candidate| candidate.has_name());
+
+    // Keep track of lifetimes which have already been replaced with other lifetimes.
+    // This makes sure that if `'a = 'b = 'c`, we don't say `'c` should be replaced by
+    // both `'a` and `'b`.
+    let mut shadowed = FxHashSet::default();
+
+    for (idx, &candidate) in lifetimes.iter().enumerate() {
+        // Don't suggest removing a lifetime twice. We only need to check this
+        // here and not up in the `victim` loop because equality is transitive,
+        // so if A = C and B = C, then A must = B, so it'll be shadowed too in
+        // A's victim loop.
+        if shadowed.contains(&candidate) {
+            continue;
+        }
+
+        for &victim in &lifetimes[(idx + 1)..] {
+            // We should only have late-bound lifetimes of the `BrNamed` variety,
+            // since we get these signatures straight from `hir_lowering`. And any
+            // other regions (ReError/ReStatic/etc.) shouldn't matter, since we
+            // can't really suggest to remove them.
+            let (ty::ReEarlyParam(ty::EarlyParamRegion { def_id, .. })
+            | ty::ReLateParam(ty::LateParamRegion {
+                bound_region: ty::BoundRegionKind::BrNamed(def_id, _),
+                ..
+            })) = victim.kind()
+            else {
+                continue;
+            };
+
+            // Do not rename lifetimes not local to this item since they'll overlap
+            // with the lint running on the parent. We still want to consider parent
+            // lifetimes which make child lifetimes redundant, otherwise we would
+            // have truncated the `identity_for_item` args above.
+            if tcx.parent(def_id) != owner_id.to_def_id() {
+                continue;
+            }
+
+            // If `candidate <: victim` and `victim <: candidate`, then they're equal.
+            if outlives_env.free_region_map().sub_free_regions(tcx, candidate, victim)
+                && outlives_env.free_region_map().sub_free_regions(tcx, victim, candidate)
+            {
+                shadowed.insert(victim);
+                tcx.emit_node_span_lint(
+                    rustc_lint_defs::builtin::REDUNDANT_LIFETIMES,
+                    tcx.local_def_id_to_hir_id(def_id.expect_local()),
+                    tcx.def_span(def_id),
+                    RedundantLifetimeArgsLint { candidate, victim },
+                );
+            }
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(hir_analysis_redundant_lifetime_args)]
+#[note]
+struct RedundantLifetimeArgsLint<'tcx> {
+    /// The lifetime we have found to be redundant.
+    victim: ty::Region<'tcx>,
+    // The lifetime we can replace the victim with.
+    candidate: ty::Region<'tcx>,
+}
+
 pub fn provide(providers: &mut Providers) {
     *providers = Providers { check_mod_type_wf, check_well_formed, ..*providers };
 }
diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
index 0b8ac9926e4..affd678fc6c 100644
--- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
+++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
@@ -20,7 +20,6 @@ use rustc_middle::hir::nested_filter;
 use rustc_middle::middle::resolve_bound_vars::*;
 use rustc_middle::query::Providers;
 use rustc_middle::ty::{self, TyCtxt, TypeSuperVisitable, TypeVisitor};
-use rustc_session::lint;
 use rustc_span::def_id::DefId;
 use rustc_span::symbol::{sym, Ident};
 use rustc_span::Span;
@@ -867,31 +866,6 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> {
             }) => {
                 self.visit_lifetime(lifetime);
                 walk_list!(self, visit_param_bound, bounds);
-
-                if lifetime.res != hir::LifetimeName::Static {
-                    for bound in bounds {
-                        let hir::GenericBound::Outlives(lt) = bound else {
-                            continue;
-                        };
-                        if lt.res != hir::LifetimeName::Static {
-                            continue;
-                        }
-                        self.insert_lifetime(lt, ResolvedArg::StaticLifetime);
-                        self.tcx.node_span_lint(
-                            lint::builtin::UNUSED_LIFETIMES,
-                            lifetime.hir_id,
-                            lifetime.ident.span,
-                            format!("unnecessary lifetime parameter `{}`", lifetime.ident),
-                            |lint| {
-                                let help = format!(
-                                    "you can use the `'static` lifetime directly, in place of `{}`",
-                                    lifetime.ident,
-                                );
-                                lint.help(help);
-                            },
-                        );
-                    }
-                }
             }
             &hir::WherePredicate::EqPredicate(hir::WhereEqPredicate { lhs_ty, rhs_ty, .. }) => {
                 self.visit_ty(lhs_ty);
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 53b5273803c..2713690f812 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -79,6 +79,7 @@ declare_lint_pass! {
         PROC_MACRO_BACK_COMPAT,
         PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
         PUB_USE_OF_PRIVATE_EXTERN_CRATE,
+        REDUNDANT_LIFETIMES,
         REFINING_IMPL_TRAIT_INTERNAL,
         REFINING_IMPL_TRAIT_REACHABLE,
         RENAMED_AND_REMOVED_LINTS,
@@ -1708,6 +1709,33 @@ declare_lint! {
 }
 
 declare_lint! {
+    /// The `redundant_lifetimes` lint detects lifetime parameters that are
+    /// redundant because they are equal to another named lifetime.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #[deny(redundant_lifetimes)]
+    ///
+    /// // `'a = 'static`, so all usages of `'a` can be replaced with `'static`
+    /// pub fn bar<'a: 'static>() {}
+    ///
+    /// // `'a = 'b`, so all usages of `'b` can be replaced with `'a`
+    /// pub fn bar<'a: 'b, 'b: 'a>() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Unused lifetime parameters may signal a mistake or unfinished code.
+    /// Consider removing the parameter.
+    pub REDUNDANT_LIFETIMES,
+    Allow,
+    "detects lifetime parameters that are redundant because they are equal to some other named lifetime"
+}
+
+declare_lint! {
     /// The `tyvar_behind_raw_pointer` lint detects raw pointer to an
     /// inference variable.
     ///
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 0daf83162db..6275c5d2a11 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -757,7 +757,7 @@ pub struct GlobalCtxt<'tcx> {
 impl<'tcx> GlobalCtxt<'tcx> {
     /// Installs `self` in a `TyCtxt` and `ImplicitCtxt` for the duration of
     /// `f`.
-    pub fn enter<'a: 'tcx, F, R>(&'a self, f: F) -> R
+    pub fn enter<F, R>(&'tcx self, f: F) -> R
     where
         F: FnOnce(TyCtxt<'tcx>) -> R,
     {
diff --git a/compiler/rustc_middle/src/ty/diagnostics.rs b/compiler/rustc_middle/src/ty/diagnostics.rs
index ee18647cdd8..cc1d6e50f6d 100644
--- a/compiler/rustc_middle/src/ty/diagnostics.rs
+++ b/compiler/rustc_middle/src/ty/diagnostics.rs
@@ -5,13 +5,13 @@ use std::fmt::Write;
 use std::ops::ControlFlow;
 
 use crate::ty::{
-    AliasTy, Const, ConstKind, FallibleTypeFolder, InferConst, InferTy, Opaque, PolyTraitPredicate,
-    Projection, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable,
-    TypeVisitor,
+    self, AliasTy, Const, ConstKind, FallibleTypeFolder, InferConst, InferTy, Opaque,
+    PolyTraitPredicate, Projection, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable,
+    TypeSuperVisitable, TypeVisitable, TypeVisitor,
 };
 
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{Applicability, Diag, DiagArgValue, IntoDiagArg};
+use rustc_errors::{into_diag_arg_using_display, Applicability, Diag, DiagArgValue, IntoDiagArg};
 use rustc_hir as hir;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::DefId;
@@ -19,10 +19,9 @@ use rustc_hir::{PredicateOrigin, WherePredicate};
 use rustc_span::{BytePos, Span};
 use rustc_type_ir::TyKind::*;
 
-impl<'tcx> IntoDiagArg for Ty<'tcx> {
-    fn into_diag_arg(self) -> DiagArgValue {
-        self.to_string().into_diag_arg()
-    }
+into_diag_arg_using_display! {
+    Ty<'_>,
+    ty::Region<'_>,
 }
 
 impl<'tcx> Ty<'tcx> {
diff --git a/tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.rs b/tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.rs
index a3f3b1a6d4d..e06341ddf31 100644
--- a/tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.rs
+++ b/tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.rs
@@ -1,8 +1,7 @@
-#![warn(unused_lifetimes)]
+#![warn(unused_lifetimes, redundant_lifetimes)]
 
 pub trait X {
-    type Y<'a: 'static>;
-    //~^ WARNING unnecessary lifetime parameter
+    type Y<'a: 'static>; //~ WARN unnecessary lifetime parameter `'a`
 }
 
 impl X for () {
diff --git a/tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.stderr b/tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.stderr
index 8d21b9172c8..4f41ec025fc 100644
--- a/tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.stderr
+++ b/tests/ui/generic-associated-types/unsatisfied-item-lifetime-bound.stderr
@@ -1,18 +1,5 @@
-warning: unnecessary lifetime parameter `'a`
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:4:12
-   |
-LL |     type Y<'a: 'static>;
-   |            ^^
-   |
-   = help: you can use the `'static` lifetime directly, in place of `'a`
-note: the lint level is defined here
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:1:9
-   |
-LL | #![warn(unused_lifetimes)]
-   |         ^^^^^^^^^^^^^^^^
-
 error[E0478]: lifetime bound not satisfied
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:9:18
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:8:18
    |
 LL |     type Y<'a: 'static>;
    |     ------------------- definition of `Y` from trait
@@ -21,7 +8,7 @@ LL |     type Y<'a> = &'a ();
    |                  ^^^^^^
    |
 note: lifetime parameter instantiated with the lifetime `'a` as defined here
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:9:12
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:8:12
    |
 LL |     type Y<'a> = &'a ();
    |            ^^
@@ -32,44 +19,57 @@ LL |     type Y<'a> = &'a () where 'a: 'static;
    |                         +++++++++++++++++
 
 error[E0478]: lifetime bound not satisfied
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:14:8
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:13:8
    |
 LL |     f: <T as X>::Y<'a>,
    |        ^^^^^^^^^^^^^^^
    |
 note: lifetime parameter instantiated with the lifetime `'a` as defined here
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:13:10
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:12:10
    |
 LL | struct B<'a, T: for<'r> X<Y<'r> = &'r ()>> {
    |          ^^
    = note: but lifetime parameter must outlive the static lifetime
 
 error[E0478]: lifetime bound not satisfied
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:19:8
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:18:8
    |
 LL |     f: <T as X>::Y<'a>,
    |        ^^^^^^^^^^^^^^^
    |
 note: lifetime parameter instantiated with the lifetime `'a` as defined here
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:18:10
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:17:10
    |
 LL | struct C<'a, T: X> {
    |          ^^
    = note: but lifetime parameter must outlive the static lifetime
 
 error[E0478]: lifetime bound not satisfied
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:24:8
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:23:8
    |
 LL |     f: <() as X>::Y<'a>,
    |        ^^^^^^^^^^^^^^^^
    |
 note: lifetime parameter instantiated with the lifetime `'a` as defined here
-  --> $DIR/unsatisfied-item-lifetime-bound.rs:23:10
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:22:10
    |
 LL | struct D<'a> {
    |          ^^
    = note: but lifetime parameter must outlive the static lifetime
 
+warning: unnecessary lifetime parameter `'a`
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:4:12
+   |
+LL |     type Y<'a: 'static>;
+   |            ^^
+   |
+   = note: you can use the `'static` lifetime directly, in place of `'a`
+note: the lint level is defined here
+  --> $DIR/unsatisfied-item-lifetime-bound.rs:1:27
+   |
+LL | #![warn(unused_lifetimes, redundant_lifetimes)]
+   |                           ^^^^^^^^^^^^^^^^^^^
+
 error: aborting due to 4 previous errors; 1 warning emitted
 
 For more information about this error, try `rustc --explain E0478`.
diff --git a/tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.rs b/tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.rs
index f6a628e97f5..bef0d70c776 100644
--- a/tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.rs
+++ b/tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.rs
@@ -8,10 +8,10 @@
 //
 //     'a : 'b
 
-#![warn(unused_lifetimes)]
+#![warn(redundant_lifetimes)]
 
-fn test<'a,'b>(x: &'a i32) -> &'b i32
-    where 'a: 'static //~ WARN unnecessary lifetime parameter `'a`
+fn test<'a,'b>(x: &'a i32) -> &'b i32 //~ WARN unnecessary lifetime parameter `'a`
+    where 'a: 'static
 {
     x
 }
diff --git a/tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.stderr b/tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.stderr
index 9f03a6553ba..d97cfd59f2b 100644
--- a/tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.stderr
+++ b/tests/ui/regions/regions-free-region-outlives-static-outlives-free-region.stderr
@@ -1,15 +1,15 @@
 warning: unnecessary lifetime parameter `'a`
-  --> $DIR/regions-free-region-outlives-static-outlives-free-region.rs:14:11
+  --> $DIR/regions-free-region-outlives-static-outlives-free-region.rs:13:9
    |
-LL |     where 'a: 'static
-   |           ^^
+LL | fn test<'a,'b>(x: &'a i32) -> &'b i32
+   |         ^^
    |
-   = help: you can use the `'static` lifetime directly, in place of `'a`
+   = note: you can use the `'static` lifetime directly, in place of `'a`
 note: the lint level is defined here
   --> $DIR/regions-free-region-outlives-static-outlives-free-region.rs:11:9
    |
-LL | #![warn(unused_lifetimes)]
-   |         ^^^^^^^^^^^^^^^^
+LL | #![warn(redundant_lifetimes)]
+   |         ^^^^^^^^^^^^^^^^^^^
 
 warning: 1 warning emitted
 
diff --git a/tests/ui/regions/regions-static-bound-rpass.rs b/tests/ui/regions/regions-static-bound-rpass.rs
index 27da42882f3..f4177f835b1 100644
--- a/tests/ui/regions/regions-static-bound-rpass.rs
+++ b/tests/ui/regions/regions-static-bound-rpass.rs
@@ -1,18 +1,19 @@
 //@ run-pass
 
-#![warn(unused_lifetimes)]
+#![warn(redundant_lifetimes)]
 
 fn invariant_id<'a,'b>(t: &'b mut &'static ()) -> &'b mut &'a ()
-    where 'a: 'static { t }
 //~^ WARN unnecessary lifetime parameter `'a`
+    where 'a: 'static { t }
 
 fn static_id<'a>(t: &'a ()) -> &'static ()
-    where 'a: 'static { t }
 //~^ WARN unnecessary lifetime parameter `'a`
+    where 'a: 'static { t }
 
 fn static_id_indirect<'a,'b>(t: &'a ()) -> &'static ()
+//~^ WARN unnecessary lifetime parameter `'a`
+//~| WARN unnecessary lifetime parameter `'b`
     where 'a: 'b, 'b: 'static { t }
-//~^ WARN unnecessary lifetime parameter `'b`
 
 fn ref_id<'a>(t: &'a ()) -> &'a () where 'static: 'a { t }
 
diff --git a/tests/ui/regions/regions-static-bound-rpass.stderr b/tests/ui/regions/regions-static-bound-rpass.stderr
index f0f3a4c5261..4199ac7bb3d 100644
--- a/tests/ui/regions/regions-static-bound-rpass.stderr
+++ b/tests/ui/regions/regions-static-bound-rpass.stderr
@@ -1,31 +1,39 @@
 warning: unnecessary lifetime parameter `'a`
-  --> $DIR/regions-static-bound-rpass.rs:6:11
+  --> $DIR/regions-static-bound-rpass.rs:5:17
    |
-LL |     where 'a: 'static { t }
-   |           ^^
+LL | fn invariant_id<'a,'b>(t: &'b mut &'static ()) -> &'b mut &'a ()
+   |                 ^^
    |
-   = help: you can use the `'static` lifetime directly, in place of `'a`
+   = note: you can use the `'static` lifetime directly, in place of `'a`
 note: the lint level is defined here
   --> $DIR/regions-static-bound-rpass.rs:3:9
    |
-LL | #![warn(unused_lifetimes)]
-   |         ^^^^^^^^^^^^^^^^
+LL | #![warn(redundant_lifetimes)]
+   |         ^^^^^^^^^^^^^^^^^^^
 
 warning: unnecessary lifetime parameter `'a`
-  --> $DIR/regions-static-bound-rpass.rs:10:11
+  --> $DIR/regions-static-bound-rpass.rs:9:14
    |
-LL |     where 'a: 'static { t }
-   |           ^^
+LL | fn static_id<'a>(t: &'a ()) -> &'static ()
+   |              ^^
    |
-   = help: you can use the `'static` lifetime directly, in place of `'a`
+   = note: you can use the `'static` lifetime directly, in place of `'a`
+
+warning: unnecessary lifetime parameter `'a`
+  --> $DIR/regions-static-bound-rpass.rs:13:23
+   |
+LL | fn static_id_indirect<'a,'b>(t: &'a ()) -> &'static ()
+   |                       ^^
+   |
+   = note: you can use the `'static` lifetime directly, in place of `'a`
 
 warning: unnecessary lifetime parameter `'b`
-  --> $DIR/regions-static-bound-rpass.rs:14:19
+  --> $DIR/regions-static-bound-rpass.rs:13:26
    |
-LL |     where 'a: 'b, 'b: 'static { t }
-   |                   ^^
+LL | fn static_id_indirect<'a,'b>(t: &'a ()) -> &'static ()
+   |                          ^^
    |
-   = help: you can use the `'static` lifetime directly, in place of `'b`
+   = note: you can use the `'static` lifetime directly, in place of `'b`
 
-warning: 3 warnings emitted
+warning: 4 warnings emitted
 
diff --git a/tests/ui/regions/regions-static-bound.rs b/tests/ui/regions/regions-static-bound.rs
index e7aa8795f01..32fa2536533 100644
--- a/tests/ui/regions/regions-static-bound.rs
+++ b/tests/ui/regions/regions-static-bound.rs
@@ -1,12 +1,13 @@
-#![warn(unused_lifetimes)]
+#![warn(unused_lifetimes, redundant_lifetimes)]
 
 fn static_id<'a,'b>(t: &'a ()) -> &'static () where 'a: 'static { t }
-//~^ WARN lifetime parameter `'b` never used
-//~| WARN unnecessary lifetime parameter `'a`
+//~^ WARN unnecessary lifetime parameter `'a`
+//~| WARN lifetime parameter `'b` never used
 
 fn static_id_indirect<'a,'b>(t: &'a ()) -> &'static ()
+//~^ WARN unnecessary lifetime parameter `'a`
+//~| WARN unnecessary lifetime parameter `'b`
     where 'a: 'b, 'b: 'static { t }
-//~^ WARN unnecessary lifetime parameter `'b`
 
 fn static_id_wrong_way<'a>(t: &'a ()) -> &'static () where 'static: 'a {
     t
diff --git a/tests/ui/regions/regions-static-bound.stderr b/tests/ui/regions/regions-static-bound.stderr
index b314e9fe85d..48aa8f32329 100644
--- a/tests/ui/regions/regions-static-bound.stderr
+++ b/tests/ui/regions/regions-static-bound.stderr
@@ -9,27 +9,40 @@ LL | fn static_id<'a,'b>(t: &'a ()) -> &'static () where 'a: 'static { t }
 note: the lint level is defined here
   --> $DIR/regions-static-bound.rs:1:9
    |
-LL | #![warn(unused_lifetimes)]
+LL | #![warn(unused_lifetimes, redundant_lifetimes)]
    |         ^^^^^^^^^^^^^^^^
 
 warning: unnecessary lifetime parameter `'a`
-  --> $DIR/regions-static-bound.rs:3:53
+  --> $DIR/regions-static-bound.rs:3:14
    |
 LL | fn static_id<'a,'b>(t: &'a ()) -> &'static () where 'a: 'static { t }
-   |                                                     ^^
+   |              ^^
    |
-   = help: you can use the `'static` lifetime directly, in place of `'a`
+   = note: you can use the `'static` lifetime directly, in place of `'a`
+note: the lint level is defined here
+  --> $DIR/regions-static-bound.rs:1:27
+   |
+LL | #![warn(unused_lifetimes, redundant_lifetimes)]
+   |                           ^^^^^^^^^^^^^^^^^^^
+
+warning: unnecessary lifetime parameter `'a`
+  --> $DIR/regions-static-bound.rs:7:23
+   |
+LL | fn static_id_indirect<'a,'b>(t: &'a ()) -> &'static ()
+   |                       ^^
+   |
+   = note: you can use the `'static` lifetime directly, in place of `'a`
 
 warning: unnecessary lifetime parameter `'b`
-  --> $DIR/regions-static-bound.rs:8:19
+  --> $DIR/regions-static-bound.rs:7:26
    |
-LL |     where 'a: 'b, 'b: 'static { t }
-   |                   ^^
+LL | fn static_id_indirect<'a,'b>(t: &'a ()) -> &'static ()
+   |                          ^^
    |
-   = help: you can use the `'static` lifetime directly, in place of `'b`
+   = note: you can use the `'static` lifetime directly, in place of `'b`
 
 error: lifetime may not live long enough
-  --> $DIR/regions-static-bound.rs:12:5
+  --> $DIR/regions-static-bound.rs:13:5
    |
 LL | fn static_id_wrong_way<'a>(t: &'a ()) -> &'static () where 'static: 'a {
    |                        -- lifetime `'a` defined here
@@ -37,7 +50,7 @@ LL |     t
    |     ^ returning this value requires that `'a` must outlive `'static`
 
 error[E0521]: borrowed data escapes outside of function
-  --> $DIR/regions-static-bound.rs:17:5
+  --> $DIR/regions-static-bound.rs:18:5
    |
 LL | fn error(u: &(), v: &()) {
    |          -  - let's call the lifetime of this reference `'1`
@@ -50,7 +63,7 @@ LL |     static_id(&u);
    |     argument requires that `'1` must outlive `'static`
 
 error[E0521]: borrowed data escapes outside of function
-  --> $DIR/regions-static-bound.rs:19:5
+  --> $DIR/regions-static-bound.rs:20:5
    |
 LL | fn error(u: &(), v: &()) {
    |                  -  - let's call the lifetime of this reference `'2`
@@ -63,6 +76,6 @@ LL |     static_id_indirect(&v);
    |     `v` escapes the function body here
    |     argument requires that `'2` must outlive `'static`
 
-error: aborting due to 3 previous errors; 3 warnings emitted
+error: aborting due to 3 previous errors; 4 warnings emitted
 
 For more information about this error, try `rustc --explain E0521`.
diff --git a/tests/ui/regions/transitively-redundant-lifetimes.rs b/tests/ui/regions/transitively-redundant-lifetimes.rs
new file mode 100644
index 00000000000..9c29f66e54c
--- /dev/null
+++ b/tests/ui/regions/transitively-redundant-lifetimes.rs
@@ -0,0 +1,20 @@
+#![deny(redundant_lifetimes)]
+
+fn a<'a, 'b>(x: &'a &'b &'a ()) {} //~ ERROR unnecessary lifetime parameter `'b`
+
+fn b<'a: 'b, 'b: 'a>() {} //~ ERROR unnecessary lifetime parameter `'b`
+
+struct Foo<T: 'static>(T);
+fn c<'a>(_: Foo<&'a ()>) {} //~ ERROR unnecessary lifetime parameter `'a`
+
+struct Bar<'a>(&'a ());
+impl<'a> Bar<'a> {
+    fn d<'b: 'a>(&'b self) {} //~ ERROR unnecessary lifetime parameter `'b`
+}
+
+fn ok(x: &'static &()) {}
+
+trait Tr<'a> {}
+impl<'a: 'static> Tr<'a> for () {} //~ ERROR unnecessary lifetime parameter `'a`
+
+fn main() {}
diff --git a/tests/ui/regions/transitively-redundant-lifetimes.stderr b/tests/ui/regions/transitively-redundant-lifetimes.stderr
new file mode 100644
index 00000000000..2d8fc433b24
--- /dev/null
+++ b/tests/ui/regions/transitively-redundant-lifetimes.stderr
@@ -0,0 +1,47 @@
+error: unnecessary lifetime parameter `'b`
+  --> $DIR/transitively-redundant-lifetimes.rs:3:10
+   |
+LL | fn a<'a, 'b>(x: &'a &'b &'a ()) {}
+   |          ^^
+   |
+   = note: you can use the `'a` lifetime directly, in place of `'b`
+note: the lint level is defined here
+  --> $DIR/transitively-redundant-lifetimes.rs:1:9
+   |
+LL | #![deny(redundant_lifetimes)]
+   |         ^^^^^^^^^^^^^^^^^^^
+
+error: unnecessary lifetime parameter `'b`
+  --> $DIR/transitively-redundant-lifetimes.rs:5:14
+   |
+LL | fn b<'a: 'b, 'b: 'a>() {}
+   |              ^^
+   |
+   = note: you can use the `'a` lifetime directly, in place of `'b`
+
+error: unnecessary lifetime parameter `'a`
+  --> $DIR/transitively-redundant-lifetimes.rs:8:6
+   |
+LL | fn c<'a>(_: Foo<&'a ()>) {}
+   |      ^^
+   |
+   = note: you can use the `'static` lifetime directly, in place of `'a`
+
+error: unnecessary lifetime parameter `'a`
+  --> $DIR/transitively-redundant-lifetimes.rs:18:6
+   |
+LL | impl<'a: 'static> Tr<'a> for () {}
+   |      ^^
+   |
+   = note: you can use the `'static` lifetime directly, in place of `'a`
+
+error: unnecessary lifetime parameter `'b`
+  --> $DIR/transitively-redundant-lifetimes.rs:12:10
+   |
+LL |     fn d<'b: 'a>(&'b self) {}
+   |          ^^
+   |
+   = note: you can use the `'a` lifetime directly, in place of `'b`
+
+error: aborting due to 5 previous errors
+