about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMark Mansi <markm@cs.wisc.edu>2018-11-28 15:05:36 -0600
committerMark Mansi <markm@cs.wisc.edu>2019-10-27 08:47:22 -0500
commit3a1847b07d552fff62be4ebf11e74f90b6a43fce (patch)
treef78a7df11927b72879242fbab8b4aeb7917a7c54
parentb7176b44a203322c834302f3b515f8c10a54f2c1 (diff)
implement outlive suggestions
-rw-r--r--src/librustc_errors/diagnostic.rs16
-rw-r--r--src/librustc_mir/borrow_check/mod.rs2
-rw-r--r--src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs25
-rw-r--r--src/librustc_mir/borrow_check/nll/region_infer/error_reporting/outlives_suggestion.rs315
-rw-r--r--src/librustc_mir/borrow_check/nll/region_infer/mod.rs12
-rw-r--r--src/test/ui/nll/closure-requirements/propagate-approximated-ref.stderr4
-rw-r--r--src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr4
-rw-r--r--src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr4
-rw-r--r--src/test/ui/nll/closure-requirements/propagate-approximated-val.stderr4
-rw-r--r--src/test/ui/nll/closure-requirements/region-lbr-named-does-not-outlive-static.stderr4
-rw-r--r--src/test/ui/nll/closure-requirements/region-lbr1-does-not-outlive-ebr2.stderr4
-rw-r--r--src/test/ui/nll/issue-52113.stderr4
-rw-r--r--src/test/ui/nll/issue-58299.stderr8
-rw-r--r--src/test/ui/nll/mir_check_cast_closure.stderr4
-rw-r--r--src/test/ui/nll/mir_check_cast_reify.stderr4
-rw-r--r--src/test/ui/nll/mir_check_cast_unsafe_fn.stderr4
-rw-r--r--src/test/ui/nll/mir_check_cast_unsize.stderr4
-rw-r--r--src/test/ui/nll/outlives-suggestion-simple.rs77
-rw-r--r--src/test/ui/nll/outlives-suggestion-simple.stderr118
-rw-r--r--src/test/ui/nll/ty-outlives/projection-one-region-closure.stderr8
-rw-r--r--src/test/ui/nll/ty-outlives/projection-one-region-trait-bound-closure.stderr8
-rw-r--r--src/test/ui/nll/ty-outlives/projection-two-region-trait-bound-closure.stderr4
-rw-r--r--src/test/ui/nll/ty-outlives/wf-unreachable.stderr32
-rw-r--r--src/test/ui/nll/type-check-pointer-coercions.stderr28
-rw-r--r--src/test/ui/nll/type-check-pointer-comparisons.stderr12
-rw-r--r--src/test/ui/nll/user-annotations/closure-substs.stderr8
-rw-r--r--src/test/ui/nll/user-annotations/inherent-associated-constants.stderr4
-rw-r--r--src/test/ui/nll/user-annotations/issue-54124.stderr4
-rw-r--r--src/test/ui/nll/user-annotations/issue-55748-pat-types-constrain-bindings.stderr12
-rw-r--r--src/test/ui/nll/user-annotations/issue-57731-ascibed-coupled-types.stderr16
-rw-r--r--src/test/ui/nll/user-annotations/patterns.stderr16
-rw-r--r--src/test/ui/nll/user-annotations/wf-self-type.stderr4
-rw-r--r--src/test/ui/nll/where_clauses_in_functions.stderr4
-rw-r--r--src/test/ui/nll/where_clauses_in_structs.stderr4
-rw-r--r--src/test/ui/regions/regions-assoc-type-in-supertrait-outlives-container.nll.stderr4
-rw-r--r--src/test/ui/regions/regions-free-region-ordering-caller.nll.stderr12
-rw-r--r--src/test/ui/regions/regions-outlives-projection-container-hrtb.nll.stderr8
-rw-r--r--src/test/ui/regions/regions-outlives-projection-container-wc.nll.stderr4
-rw-r--r--src/test/ui/regions/regions-static-bound.nll.stderr4
39 files changed, 807 insertions, 6 deletions
diff --git a/src/librustc_errors/diagnostic.rs b/src/librustc_errors/diagnostic.rs
index 1781f2e1650..3c4539b597b 100644
--- a/src/librustc_errors/diagnostic.rs
+++ b/src/librustc_errors/diagnostic.rs
@@ -6,7 +6,7 @@ use crate::Applicability;
 use crate::Level;
 use crate::snippet::Style;
 use std::fmt;
-use syntax_pos::{MultiSpan, Span};
+use syntax_pos::{MultiSpan, Span, DUMMY_SP};
 
 #[must_use]
 #[derive(Clone, Debug, PartialEq, Hash, RustcEncodable, RustcDecodable)]
@@ -17,6 +17,11 @@ pub struct Diagnostic {
     pub span: MultiSpan,
     pub children: Vec<SubDiagnostic>,
     pub suggestions: Vec<CodeSuggestion>,
+
+    /// This is not used for highlighting or rendering any error message.  Rather, it can be used
+    /// as a sort key to sort a buffer of diagnostics.  By default, it is the primary span of
+    /// `span` if there is one.  Otherwise, it is `DUMMY_SP`.
+    pub sort_span: Span,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
@@ -87,6 +92,7 @@ impl Diagnostic {
             span: MultiSpan::new(),
             children: vec![],
             suggestions: vec![],
+            sort_span: DUMMY_SP,
         }
     }
 
@@ -118,6 +124,11 @@ impl Diagnostic {
         self.level == Level::Cancelled
     }
 
+    /// Set the sorting span.
+    pub fn set_sort_span(&mut self, sp: Span) {
+        self.sort_span = sp;
+    }
+
     /// Adds a span/label to be included in the resulting snippet.
     /// This label will be shown together with the original span/label used when creating the
     /// diagnostic, *not* a span added by one of the `span_*` methods.
@@ -457,6 +468,9 @@ impl Diagnostic {
 
     pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
         self.span = sp.into();
+        if let Some(span) = self.span.primary_span() {
+            self.sort_span = span;
+        }
         self
     }
 
diff --git a/src/librustc_mir/borrow_check/mod.rs b/src/librustc_mir/borrow_check/mod.rs
index c3369e87215..4bfd0844f84 100644
--- a/src/librustc_mir/borrow_check/mod.rs
+++ b/src/librustc_mir/borrow_check/mod.rs
@@ -367,7 +367,7 @@ fn do_mir_borrowck<'a, 'tcx>(
     }
 
     if !mbcx.errors_buffer.is_empty() {
-        mbcx.errors_buffer.sort_by_key(|diag| diag.span.primary_span());
+        mbcx.errors_buffer.sort_by_key(|diag| diag.sort_span);
 
         for diag in mbcx.errors_buffer.drain(..) {
             mbcx.infcx.tcx.sess.diagnostic().emit_diagnostic(&diag);
diff --git a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs b/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs
index 7362ae9c638..26b288cb4b2 100644
--- a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs
+++ b/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs
@@ -18,6 +18,10 @@ use syntax::errors::Applicability;
 use syntax::symbol::kw;
 use syntax_pos::Span;
 
+use self::outlives_suggestion::OutlivesSuggestionBuilder;
+
+pub mod outlives_suggestion;
+
 mod region_name;
 mod var_name;
 
@@ -56,7 +60,6 @@ enum Trace {
 /// Various pieces of state used when reporting borrow checker errors.
 pub struct ErrorReportingCtx<'a, 'b, 'tcx> {
     /// The region inference context used for borrow chekcing this MIR body.
-    #[allow(dead_code)] // FIXME(mark-i-m): used by outlives suggestions
     region_infcx: &'b RegionInferenceContext<'tcx>,
 
     /// The inference context used for type checking.
@@ -370,6 +373,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         fr: RegionVid,
         fr_origin: NLLRegionVariableOrigin,
         outlived_fr: RegionVid,
+        outlives_suggestion: &mut OutlivesSuggestionBuilder,
         renctx: &mut RegionErrorNamingCtx,
     ) -> DiagnosticBuilder<'a> {
         debug!("report_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
@@ -415,9 +419,22 @@ impl<'tcx> RegionInferenceContext<'tcx> {
                 self.report_fnmut_error(&errctx, &errci, renctx)
             }
             (ConstraintCategory::Assignment, true, false)
-            | (ConstraintCategory::CallArgument, true, false) =>
-                self.report_escaping_data_error(&errctx, &errci, renctx),
-            _ => self.report_general_error(&errctx, &errci, renctx),
+            | (ConstraintCategory::CallArgument, true, false) => {
+                let mut db = self.report_escaping_data_error(&errctx, &errci, renctx);
+
+                outlives_suggestion.intermediate_suggestion(&errctx, &errci, renctx, &mut db);
+                outlives_suggestion.collect_constraint(fr, outlived_fr);
+
+                db
+            }
+            _ => {
+                let mut db = self.report_general_error(&errctx, &errci, renctx);
+
+                outlives_suggestion.intermediate_suggestion(&errctx, &errci, renctx, &mut db);
+                outlives_suggestion.collect_constraint(fr, outlived_fr);
+
+                db
+            }
         }
     }
 
diff --git a/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/outlives_suggestion.rs b/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/outlives_suggestion.rs
new file mode 100644
index 00000000000..a55a3a40604
--- /dev/null
+++ b/src/librustc_mir/borrow_check/nll/region_infer/error_reporting/outlives_suggestion.rs
@@ -0,0 +1,315 @@
+//! Contains utilities for generating suggestions for borrowck errors related to unsatisified
+//! outlives constraints.
+
+use std::collections::BTreeMap;
+
+use log::debug;
+use rustc::{hir::def_id::DefId, infer::InferCtxt, mir::Body, ty::RegionVid};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{Diagnostic, DiagnosticBuilder, Level};
+
+use smallvec::SmallVec;
+
+use crate::borrow_check::nll::region_infer::{
+    error_reporting::{
+        region_name::{RegionName, RegionNameSource},
+        ErrorConstraintInfo, ErrorReportingCtx, RegionErrorNamingCtx,
+    },
+    RegionInferenceContext,
+};
+
+/// The different things we could suggest.
+enum SuggestedConstraint {
+    /// Outlives(a, [b, c, d, ...]) => 'a: 'b + 'c + 'd + ...
+    Outlives(RegionName, SmallVec<[RegionName; 2]>),
+
+    /// 'a = 'b
+    Equal(RegionName, RegionName),
+
+    /// 'a: 'static i.e. 'a = 'static and the user should just use 'static
+    Static(RegionName),
+}
+
+/// Collects information about outlives constraints that needed to be added for a given MIR node
+/// corresponding to a function definition.
+///
+/// Adds a help note suggesting adding a where clause with the needed constraints.
+pub struct OutlivesSuggestionBuilder {
+    /// The MIR DefId of the fn with the lifetime error.
+    mir_def_id: DefId,
+
+    /// The list of outlives constraints that need to be added. Specifically, we map each free
+    /// region to all other regions that it must outlive. I will use the shorthand `fr:
+    /// outlived_frs`. Not all of these regions will already have names necessarily. Some could be
+    /// implicit free regions that we inferred. These will need to be given names in the final
+    /// suggestion message.
+    constraints_to_add: BTreeMap<RegionVid, Vec<RegionVid>>,
+}
+
+impl OutlivesSuggestionBuilder {
+    /// Create a new builder for the given MIR node representing a fn definition.
+    crate fn new(mir_def_id: DefId) -> Self {
+        OutlivesSuggestionBuilder { mir_def_id, constraints_to_add: BTreeMap::default() }
+    }
+
+    /// Returns `true` iff the `RegionNameSource` is a valid source for an outlives
+    /// suggestion.
+    //
+    // FIXME: Currently, we only report suggestions if the `RegionNameSource` is an early-bound
+    // region or a named region, avoiding using regions with synthetic names altogether. This
+    // allows us to avoid giving impossible suggestions (e.g. adding bounds to closure args).
+    // We can probably be less conservative, since some inferred free regions are namable (e.g.
+    // the user can explicitly name them. To do this, we would allow some regions whose names
+    // come from `MatchedAdtAndSegment`, being careful to filter out bad suggestions, such as
+    // naming the `'self` lifetime in methods, etc.
+    fn region_name_is_suggestable(name: &RegionName) -> bool {
+        match name.source {
+            RegionNameSource::NamedEarlyBoundRegion(..)
+            | RegionNameSource::NamedFreeRegion(..)
+            | RegionNameSource::Static => {
+                debug!("Region {:?} is suggestable", name);
+                true
+            }
+
+            // Don't give suggestions for upvars, closure return types, or other unnamable
+            // regions.
+            RegionNameSource::SynthesizedFreeEnvRegion(..)
+            | RegionNameSource::CannotMatchHirTy(..)
+            | RegionNameSource::MatchedHirTy(..)
+            | RegionNameSource::MatchedAdtAndSegment(..)
+            | RegionNameSource::AnonRegionFromUpvar(..)
+            | RegionNameSource::AnonRegionFromOutput(..)
+            | RegionNameSource::AnonRegionFromYieldTy(..) => {
+                debug!("Region {:?} is NOT suggestable", name);
+                false
+            }
+        }
+    }
+
+    /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`.
+    fn region_vid_to_name(
+        &self,
+        errctx: &ErrorReportingCtx<'_, '_, '_>,
+        renctx: &mut RegionErrorNamingCtx,
+        region: RegionVid,
+    ) -> Option<RegionName> {
+        errctx
+            .region_infcx
+            .give_region_a_name(errctx, renctx, region)
+            .filter(Self::region_name_is_suggestable)
+    }
+
+    /// Compiles a list of all suggestions to be printed in the final big suggestion.
+    fn compile_all_suggestions<'tcx>(
+        &self,
+        body: &Body<'tcx>,
+        region_infcx: &RegionInferenceContext<'tcx>,
+        infcx: &InferCtxt<'_, 'tcx>,
+        renctx: &mut RegionErrorNamingCtx,
+    ) -> SmallVec<[SuggestedConstraint; 2]> {
+        let mut suggested = SmallVec::new();
+
+        // Keep track of variables that we have already suggested unifying so that we don't print
+        // out silly duplicate messages.
+        let mut unified_already = FxHashSet::default();
+
+        let errctx = ErrorReportingCtx {
+            region_infcx,
+            infcx,
+            body,
+            mir_def_id: self.mir_def_id,
+
+            // We should not be suggesting naming upvars, so we pass in a dummy set of upvars that
+            // should never be used.
+            upvars: &[],
+        };
+
+        for (fr, outlived) in &self.constraints_to_add {
+            let fr_name = if let Some(fr_name) = self.region_vid_to_name(&errctx, renctx, *fr) {
+                fr_name
+            } else {
+                continue;
+            };
+
+            let outlived = outlived
+                .iter()
+                // if there is a `None`, we will just omit that constraint
+                .filter_map(|fr| {
+                    self.region_vid_to_name(&errctx, renctx, *fr).map(|rname| (fr, rname))
+                })
+                .collect::<Vec<_>>();
+
+            // No suggestable outlived lifetimes.
+            if outlived.is_empty() {
+                continue;
+            }
+
+            // There are three types of suggestions we can make:
+            // 1) Suggest a bound: 'a: 'b
+            // 2) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we
+            //    should just replace 'a with 'static.
+            // 3) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a
+
+            if outlived.iter().any(|(_, outlived_name)| {
+                if let RegionNameSource::Static = outlived_name.source {
+                    true
+                } else {
+                    false
+                }
+            }) {
+                suggested.push(SuggestedConstraint::Static(fr_name));
+            } else {
+                // We want to isolate out all lifetimes that should be unified and print out
+                // separate messages for them.
+
+                let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition(
+                    // Do we have both 'fr: 'r and 'r: 'fr?
+                    |(r, _)| {
+                        self.constraints_to_add
+                            .get(r)
+                            .map(|r_outlived| r_outlived.as_slice().contains(fr))
+                            .unwrap_or(false)
+                    },
+                );
+
+                for (r, bound) in unified.into_iter() {
+                    if !unified_already.contains(fr) {
+                        suggested.push(SuggestedConstraint::Equal(fr_name.clone(), bound));
+                        unified_already.insert(r);
+                    }
+                }
+
+                if !other.is_empty() {
+                    let other =
+                        other.iter().map(|(_, rname)| rname.clone()).collect::<SmallVec<_>>();
+                    suggested.push(SuggestedConstraint::Outlives(fr_name, other))
+                }
+            }
+        }
+
+        suggested
+    }
+
+    /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest.
+    crate fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) {
+        debug!("Collected {:?}: {:?}", fr, outlived_fr);
+
+        // Add to set of constraints for final help note.
+        self.constraints_to_add.entry(fr).or_insert(Vec::new()).push(outlived_fr);
+    }
+
+    /// Emit an intermediate note on the given `Diagnostic` if the involved regions are
+    /// suggestable.
+    crate fn intermediate_suggestion(
+        &mut self,
+        errctx: &ErrorReportingCtx<'_, '_, '_>,
+        errci: &ErrorConstraintInfo,
+        renctx: &mut RegionErrorNamingCtx,
+        diag: &mut DiagnosticBuilder<'_>,
+    ) {
+        // Emit an intermediate note.
+        let fr_name = self.region_vid_to_name(errctx, renctx, errci.fr);
+        let outlived_fr_name = self.region_vid_to_name(errctx, renctx, errci.outlived_fr);
+
+        if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name) {
+            if let RegionNameSource::Static = outlived_fr_name.source {
+                diag.help(&format!("consider replacing `{}` with `'static`", fr_name));
+            } else {
+                diag.help(&format!(
+                    "consider adding the following bound: `{}: {}`",
+                    fr_name, outlived_fr_name
+                ));
+            }
+        }
+    }
+
+    /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final
+    /// suggestion including all collected constraints.
+    crate fn add_suggestion<'tcx>(
+        &self,
+        body: &Body<'tcx>,
+        region_infcx: &RegionInferenceContext<'tcx>,
+        infcx: &InferCtxt<'_, 'tcx>,
+        errors_buffer: &mut Vec<Diagnostic>,
+        renctx: &mut RegionErrorNamingCtx,
+    ) {
+        // No constraints to add? Done.
+        if self.constraints_to_add.is_empty() {
+            debug!("No constraints to suggest.");
+            return;
+        }
+
+        // If there is only one constraint to suggest, then we already suggested it in the
+        // intermediate suggestion above.
+        if self.constraints_to_add.len() == 1 {
+            debug!("Only 1 suggestion. Skipping.");
+            return;
+        }
+
+        // Get all suggestable constraints.
+        let suggested = self.compile_all_suggestions(body, region_infcx, infcx, renctx);
+
+        // If there are no suggestable constraints...
+        if suggested.is_empty() {
+            debug!("Only 1 suggestable constraint. Skipping.");
+            return;
+        }
+
+        // If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a
+        // list of diagnostics.
+        let mut diag = if suggested.len() == 1 {
+            DiagnosticBuilder::new(
+                infcx.tcx.sess.diagnostic(),
+                Level::Help,
+                &match suggested.last().unwrap() {
+                    SuggestedConstraint::Outlives(a, bs) => {
+                        let bs: SmallVec<[String; 2]> =
+                            bs.iter().map(|r| format!("{}", r)).collect();
+                        format!("add bound `{}: {}`", a, bs.join(" + "))
+                    }
+
+                    SuggestedConstraint::Equal(a, b) => {
+                        format!("`{}` and `{}` must be the same: replace one with the other", a, b)
+                    }
+                    SuggestedConstraint::Static(a) => format!("replace `{}` with `'static`", a),
+                },
+            )
+        } else {
+            // Create a new diagnostic.
+            let mut diag = DiagnosticBuilder::new(
+                infcx.tcx.sess.diagnostic(),
+                Level::Help,
+                "the following changes may resolve your lifetime errors",
+            );
+
+            // Add suggestions.
+            for constraint in suggested {
+                match constraint {
+                    SuggestedConstraint::Outlives(a, bs) => {
+                        let bs: SmallVec<[String; 2]> =
+                            bs.iter().map(|r| format!("{}", r)).collect();
+                        diag.help(&format!("add bound `{}: {}`", a, bs.join(" + ")));
+                    }
+                    SuggestedConstraint::Equal(a, b) => {
+                        diag.help(&format!(
+                            "`{}` and `{}` must be the same: replace one with the other",
+                            a, b
+                        ));
+                    }
+                    SuggestedConstraint::Static(a) => {
+                        diag.help(&format!("replace `{}` with `'static`", a));
+                    }
+                }
+            }
+
+            diag
+        };
+
+        // We want this message to appear after other messages on the mir def.
+        let mir_span = infcx.tcx.def_span(self.mir_def_id);
+        diag.sort_span = mir_span.shrink_to_hi();
+
+        // Buffer the diagnostic
+        diag.buffer(errors_buffer);
+    }
+}
diff --git a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs
index dbb810db555..4de8200e6a0 100644
--- a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs
+++ b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs
@@ -11,6 +11,7 @@ use crate::borrow_check::nll::{
     region_infer::values::{
         PlaceholderIndices, RegionElement, ToElementIndex
     },
+    region_infer::error_reporting::outlives_suggestion::OutlivesSuggestionBuilder,
     type_check::{free_region_relations::UniversalRegionRelations, Locations},
 };
 use crate::borrow_check::Upvar;
@@ -1326,6 +1327,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         errors_buffer: &mut Vec<Diagnostic>,
         region_naming: &mut RegionErrorNamingCtx,
     ) {
+        let mut outlives_suggestion = OutlivesSuggestionBuilder::new(mir_def_id);
+
         for (fr, fr_definition) in self.definitions.iter_enumerated() {
             match fr_definition.origin {
                 NLLRegionVariableOrigin::FreeRegion => {
@@ -1339,6 +1342,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
                         mir_def_id,
                         fr,
                         &mut propagated_outlives_requirements,
+                        &mut outlives_suggestion,
                         errors_buffer,
                         region_naming,
                     );
@@ -1353,6 +1357,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
                 }
             }
         }
+
+        // Emit outlives suggestions
+        outlives_suggestion.add_suggestion(body, self, infcx, errors_buffer, region_naming);
     }
 
     /// Checks the final value for the free region `fr` to see if it
@@ -1371,6 +1378,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         mir_def_id: DefId,
         longer_fr: RegionVid,
         propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+        outlives_suggestion: &mut OutlivesSuggestionBuilder,
         errors_buffer: &mut Vec<Diagnostic>,
         region_naming: &mut RegionErrorNamingCtx,
     ) {
@@ -1399,6 +1407,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
                 upvars,
                 mir_def_id,
                 propagated_outlives_requirements,
+                outlives_suggestion,
                 errors_buffer,
                 region_naming,
             );
@@ -1416,6 +1425,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
                 upvars,
                 mir_def_id,
                 propagated_outlives_requirements,
+                outlives_suggestion,
                 errors_buffer,
                 region_naming,
             ) {
@@ -1438,6 +1448,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         upvars: &[Upvar],
         mir_def_id: DefId,
         propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+        outlives_suggestion: &mut OutlivesSuggestionBuilder,
         errors_buffer: &mut Vec<Diagnostic>,
         region_naming: &mut RegionErrorNamingCtx,
     ) -> Option<ErrorReported> {
@@ -1497,6 +1508,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
             longer_fr,
             NLLRegionVariableOrigin::FreeRegion,
             shorter_fr,
+            outlives_suggestion,
             region_naming,
         );
 
diff --git a/src/test/ui/nll/closure-requirements/propagate-approximated-ref.stderr b/src/test/ui/nll/closure-requirements/propagate-approximated-ref.stderr
index fa8384311ea..61f20d1dc9a 100644
--- a/src/test/ui/nll/closure-requirements/propagate-approximated-ref.stderr
+++ b/src/test/ui/nll/closure-requirements/propagate-approximated-ref.stderr
@@ -43,5 +43,9 @@ LL | fn supply<'a, 'b>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>) {
 LL |         demand_y(x, y, x.get())
    |         ^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr b/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr
index 601b3577e0e..e34884e9f9a 100644
--- a/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr
+++ b/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr
@@ -46,5 +46,9 @@ LL | |         demand_y(x, y, x.get())
 LL | |     });
    | |______^ `cell_a` escapes the function body here
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr b/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr
index 5b5440e7a96..40917135c59 100644
--- a/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr
+++ b/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr
@@ -46,5 +46,9 @@ LL | |         demand_y(x, y, x.get())
 LL | |     });
    | |______^ `cell_a` escapes the function body here
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/closure-requirements/propagate-approximated-val.stderr b/src/test/ui/nll/closure-requirements/propagate-approximated-val.stderr
index a08cde2c9c6..f227e9eb413 100644
--- a/src/test/ui/nll/closure-requirements/propagate-approximated-val.stderr
+++ b/src/test/ui/nll/closure-requirements/propagate-approximated-val.stderr
@@ -43,5 +43,9 @@ LL | fn test<'a, 'b>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>) {
 LL |         demand_y(outlives1, outlives2, x.get())
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/closure-requirements/region-lbr-named-does-not-outlive-static.stderr b/src/test/ui/nll/closure-requirements/region-lbr-named-does-not-outlive-static.stderr
index d0a24a267fd..5e8d9fa7882 100644
--- a/src/test/ui/nll/closure-requirements/region-lbr-named-does-not-outlive-static.stderr
+++ b/src/test/ui/nll/closure-requirements/region-lbr-named-does-not-outlive-static.stderr
@@ -6,5 +6,9 @@ LL | fn foo<'a>(x: &'a u32) -> &'static u32 {
 LL |     &*x
    |     ^^^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/closure-requirements/region-lbr1-does-not-outlive-ebr2.stderr b/src/test/ui/nll/closure-requirements/region-lbr1-does-not-outlive-ebr2.stderr
index 6dc98a94408..d3edbfdae88 100644
--- a/src/test/ui/nll/closure-requirements/region-lbr1-does-not-outlive-ebr2.stderr
+++ b/src/test/ui/nll/closure-requirements/region-lbr1-does-not-outlive-ebr2.stderr
@@ -8,5 +8,9 @@ LL | fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'b u32 {
 LL |     &*x
    |     ^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/issue-52113.stderr b/src/test/ui/nll/issue-52113.stderr
index 590963ded78..109c7e95659 100644
--- a/src/test/ui/nll/issue-52113.stderr
+++ b/src/test/ui/nll/issue-52113.stderr
@@ -9,5 +9,9 @@ LL | fn produce_err<'a, 'b: 'a>(data: &'b mut Vec<&'b u32>, value: &'a u32) -> i
 LL |     x
    |     ^ returning this value requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/issue-58299.stderr b/src/test/ui/nll/issue-58299.stderr
index aba07542d02..906a7bff00d 100644
--- a/src/test/ui/nll/issue-58299.stderr
+++ b/src/test/ui/nll/issue-58299.stderr
@@ -7,6 +7,10 @@ LL | fn foo<'a>(x: i32) {
 LL |         A::<'a>::X..=A::<'static>::X => (),
    |         ^^^^^^^^^^ requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/issue-58299.rs:24:27
    |
@@ -16,5 +20,9 @@ LL | fn bar<'a>(x: i32) {
 LL |         A::<'static>::X..=A::<'a>::X => (),
    |                           ^^^^^^^^^^ requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to 2 previous errors
 
diff --git a/src/test/ui/nll/mir_check_cast_closure.stderr b/src/test/ui/nll/mir_check_cast_closure.stderr
index e14cb074c81..40ce9e850ea 100644
--- a/src/test/ui/nll/mir_check_cast_closure.stderr
+++ b/src/test/ui/nll/mir_check_cast_closure.stderr
@@ -9,5 +9,9 @@ LL |     let g: fn(_, _) -> _ = |_x, y| y;
 LL |     g
    |     ^ returning this value requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/mir_check_cast_reify.stderr b/src/test/ui/nll/mir_check_cast_reify.stderr
index 4e8eec330a5..f091161b224 100644
--- a/src/test/ui/nll/mir_check_cast_reify.stderr
+++ b/src/test/ui/nll/mir_check_cast_reify.stderr
@@ -7,5 +7,9 @@ LL | fn bar<'a>(x: &'a u32) -> &'static u32 {
 LL |     f(x)
    |     ^^^^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/mir_check_cast_unsafe_fn.stderr b/src/test/ui/nll/mir_check_cast_unsafe_fn.stderr
index 52959850a33..2f76758f259 100644
--- a/src/test/ui/nll/mir_check_cast_unsafe_fn.stderr
+++ b/src/test/ui/nll/mir_check_cast_unsafe_fn.stderr
@@ -7,5 +7,9 @@ LL | fn bar<'a>(input: &'a u32, f: fn(&'a u32) -> &'a u32) -> &'static u32 {
 LL |     unsafe { g(input) }
    |              ^^^^^^^^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/mir_check_cast_unsize.stderr b/src/test/ui/nll/mir_check_cast_unsize.stderr
index 364d6c17ea7..4a76fa8158e 100644
--- a/src/test/ui/nll/mir_check_cast_unsize.stderr
+++ b/src/test/ui/nll/mir_check_cast_unsize.stderr
@@ -6,5 +6,9 @@ LL | fn bar<'a>(x: &'a u32) -> &'static dyn Debug {
 LL |     x
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/outlives-suggestion-simple.rs b/src/test/ui/nll/outlives-suggestion-simple.rs
new file mode 100644
index 00000000000..91a7a1d71e8
--- /dev/null
+++ b/src/test/ui/nll/outlives-suggestion-simple.rs
@@ -0,0 +1,77 @@
+// Test the simplest of outlives suggestions.
+
+#![feature(nll)]
+
+fn foo1<'a, 'b>(x: &'a usize) -> &'b usize {
+    x //~ERROR lifetime may not live long enough
+}
+
+fn foo2<'a>(x: &'a usize) -> &'static usize {
+    x //~ERROR lifetime may not live long enough
+}
+
+fn foo3<'a, 'b>(x: &'a usize, y: &'b usize) -> (&'b usize, &'a usize) {
+    (x, y) //~ERROR lifetime may not live long enough
+           //~^ERROR lifetime may not live long enough
+}
+
+fn foo4<'a, 'b, 'c>(x: &'a usize) -> (&'b usize, &'c usize) {
+    // FIXME: ideally, we suggest 'a: 'b + 'c, but as of today (may 04, 2019), the null error
+    // reporting stops after the first error in a MIR def so as not to produce too many errors, so
+    // currently we only report 'a: 'b. The user would then re-run and get another error.
+    (x, x) //~ERROR lifetime may not live long enough
+}
+
+struct Foo<'a> {
+    x: &'a usize,
+}
+
+impl Foo<'static> {
+    pub fn foo<'a>(x: &'a usize) -> Self {
+        Foo { x } //~ERROR lifetime may not live long enough
+    }
+}
+
+struct Bar<'a> {
+    x: &'a usize,
+}
+
+impl<'a> Bar<'a> {
+    pub fn get<'b>(&self) -> &'b usize {
+        self.x //~ERROR lifetime may not live long enough
+    }
+}
+
+// source: https://stackoverflow.com/questions/41417057/why-do-i-get-a-lifetime-error-when-i-use-a-mutable-reference-in-a-struct-instead
+struct Baz<'a> {
+    x: &'a mut i32,
+}
+
+impl<'a> Baz<'a> {
+    fn get<'b>(&'b self) -> &'a i32 {
+        self.x //~ERROR lifetime may not live long enough
+    }
+}
+
+// source: https://stackoverflow.com/questions/41204134/rust-lifetime-error
+struct Bar2<'a> {
+    bar: &'a str,
+}
+impl<'a> Bar2<'a> {
+    fn new(foo: &'a Foo2<'a>) -> Bar2<'a> {
+        Bar2 { bar: foo.raw }
+    }
+}
+
+pub struct Foo2<'a> {
+    raw: &'a str,
+    cell: std::cell::Cell<&'a str>,
+}
+impl<'a> Foo2<'a> {
+    // should not produce outlives suggestions to name 'self
+    fn get_bar(&self) -> Bar2 {
+        Bar2::new(&self) //~ERROR borrowed data escapes outside of function
+    }
+}
+
+fn main() {}
diff --git a/src/test/ui/nll/outlives-suggestion-simple.stderr b/src/test/ui/nll/outlives-suggestion-simple.stderr
new file mode 100644
index 00000000000..90c6bf2f5b8
--- /dev/null
+++ b/src/test/ui/nll/outlives-suggestion-simple.stderr
@@ -0,0 +1,118 @@
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:6:5
+   |
+LL | fn foo1<'a, 'b>(x: &'a usize) -> &'b usize {
+   |         --  -- lifetime `'b` defined here
+   |         |
+   |         lifetime `'a` defined here
+LL |     x
+   |     ^ returning this value requires that `'a` must outlive `'b`
+
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:10:5
+   |
+LL | fn foo2<'a>(x: &'a usize) -> &'static usize {
+   |         -- lifetime `'a` defined here
+LL |     x
+   |     ^ returning this value requires that `'a` must outlive `'static`
+
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:14:5
+   |
+LL | fn foo3<'a, 'b>(x: &'a usize, y: &'b usize) -> (&'b usize, &'a usize) {
+   |         --  -- lifetime `'b` defined here
+   |         |
+   |         lifetime `'a` defined here
+LL |     (x, y)
+   |     ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:14:5
+   |
+LL | fn foo3<'a, 'b>(x: &'a usize, y: &'b usize) -> (&'b usize, &'a usize) {
+   |         --  -- lifetime `'b` defined here
+   |         |
+   |         lifetime `'a` defined here
+LL |     (x, y)
+   |     ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
+
+help: the following changes may resolve your lifetime errors
+   |
+   = help: `'a` and `'b` must be the same; replace one with the other
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:22:5
+   |
+LL | fn foo4<'a, 'b, 'c>(x: &'a usize) -> (&'b usize, &'c usize) {
+   |         --  -- lifetime `'b` defined here
+   |         |
+   |         lifetime `'a` defined here
+...
+LL |     (x, x)
+   |     ^^^^^^ returning this value requires that `'a` must outlive `'b`
+
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:31:9
+   |
+LL |     pub fn foo<'a>(x: &'a usize) -> Self {
+   |                -- lifetime `'a` defined here
+LL |         Foo { x }
+   |         ^^^^^^^^^ returning this value requires that `'a` must outlive `'static`
+
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:41:9
+   |
+LL | impl<'a> Bar<'a> {
+   |      -- lifetime `'a` defined here
+LL |     pub fn get<'b>(&self) -> &'b usize {
+   |                -- lifetime `'b` defined here
+LL |         self.x
+   |         ^^^^^^ returning this value requires that `'a` must outlive `'b`
+
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:52:9
+   |
+LL | impl<'a> Baz<'a> {
+   |      -- lifetime `'a` defined here
+LL |     fn get<'b>(&'b self) -> &'a i32 {
+   |            -- lifetime `'b` defined here
+LL |         self.x
+   |         ^^^^^^ returning this value requires that `'b` must outlive `'a`
+
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
+error[E0521]: borrowed data escapes outside of function
+  --> $DIR/outlives-suggestion-simple.rs:73:9
+   |
+LL |     fn get_bar(&self) -> Bar2 {
+   |                -----
+   |                |
+   |                `self` is declared here, outside of the function body
+   |                `self` is a reference that is only valid in the function body
+LL |         Bar2::new(&self)
+   |         ^^^^^^^^^^^^^^^^ `self` escapes the function body here
+
+error: aborting due to 9 previous errors
+
diff --git a/src/test/ui/nll/ty-outlives/projection-one-region-closure.stderr b/src/test/ui/nll/ty-outlives/projection-one-region-closure.stderr
index 10b2bd1af47..be45c23ed83 100644
--- a/src/test/ui/nll/ty-outlives/projection-one-region-closure.stderr
+++ b/src/test/ui/nll/ty-outlives/projection-one-region-closure.stderr
@@ -51,6 +51,10 @@ LL | fn no_relationships_late<'a, 'b, T>(cell: Cell<&'a ()>, t: T)
 LL |     with_signature(cell, t, |cell, t| require(cell, t));
    |                                       ^^^^^^^^^^^^^^^^ argument requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 note: External requirements
   --> $DIR/projection-one-region-closure.rs:56:29
    |
@@ -105,6 +109,10 @@ LL | fn no_relationships_early<'a, 'b, T>(cell: Cell<&'a ()>, t: T)
 LL |     with_signature(cell, t, |cell, t| require(cell, t));
    |                                       ^^^^^^^^^^^^^^^^ argument requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 note: External requirements
   --> $DIR/projection-one-region-closure.rs:70:29
    |
diff --git a/src/test/ui/nll/ty-outlives/projection-one-region-trait-bound-closure.stderr b/src/test/ui/nll/ty-outlives/projection-one-region-trait-bound-closure.stderr
index b4b74bfc128..39f9e78f889 100644
--- a/src/test/ui/nll/ty-outlives/projection-one-region-trait-bound-closure.stderr
+++ b/src/test/ui/nll/ty-outlives/projection-one-region-trait-bound-closure.stderr
@@ -42,6 +42,10 @@ LL | fn no_relationships_late<'a, 'b, T>(cell: Cell<&'a ()>, t: T)
 LL |     with_signature(cell, t, |cell, t| require(cell, t));
    |                                       ^^^^^^^^^^^^^^^^ argument requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 note: External requirements
   --> $DIR/projection-one-region-trait-bound-closure.rs:47:29
    |
@@ -87,6 +91,10 @@ LL | fn no_relationships_early<'a, 'b, T>(cell: Cell<&'a ()>, t: T)
 LL |     with_signature(cell, t, |cell, t| require(cell, t));
    |                                       ^^^^^^^^^^^^^^^^ argument requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 note: External requirements
   --> $DIR/projection-one-region-trait-bound-closure.rs:60:29
    |
diff --git a/src/test/ui/nll/ty-outlives/projection-two-region-trait-bound-closure.stderr b/src/test/ui/nll/ty-outlives/projection-two-region-trait-bound-closure.stderr
index a48766cd734..15e921bdec1 100644
--- a/src/test/ui/nll/ty-outlives/projection-two-region-trait-bound-closure.stderr
+++ b/src/test/ui/nll/ty-outlives/projection-two-region-trait-bound-closure.stderr
@@ -237,6 +237,10 @@ LL | fn two_regions<'a, 'b, T>(cell: Cell<&'a ()>, t: T)
 LL |     with_signature(cell, t, |cell, t| require(cell, t));
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^ closure body requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 note: External requirements
   --> $DIR/projection-two-region-trait-bound-closure.rs:97:29
    |
diff --git a/src/test/ui/nll/ty-outlives/wf-unreachable.stderr b/src/test/ui/nll/ty-outlives/wf-unreachable.stderr
index 9128fd16479..e4fa647e6cb 100644
--- a/src/test/ui/nll/ty-outlives/wf-unreachable.stderr
+++ b/src/test/ui/nll/ty-outlives/wf-unreachable.stderr
@@ -7,6 +7,10 @@ LL |     return;
 LL |     let x: &'static &'a ();
    |            ^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/wf-unreachable.rs:13:12
    |
@@ -16,6 +20,10 @@ LL |     return;
 LL |     let x: &'static &'a () = &&();
    |            ^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/wf-unreachable.rs:17:12
    |
@@ -24,6 +32,10 @@ LL | fn uninit_infer<'a>() {
 LL |     let x: &'static &'a _;
    |            ^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/wf-unreachable.rs:23:12
    |
@@ -33,6 +45,10 @@ LL |     return;
 LL |     let x: &'static &'a _ = &&();
    |            ^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/wf-unreachable.rs:28:12
    |
@@ -42,6 +58,10 @@ LL |     return;
 LL |     let _: &'static &'a ();
    |            ^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/wf-unreachable.rs:33:12
    |
@@ -51,6 +71,10 @@ LL |     return;
 LL |     let _: &'static &'a () = &&();
    |            ^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/wf-unreachable.rs:38:12
    |
@@ -60,6 +84,10 @@ LL |     return;
 LL |     let _: &'static &'a _ = &&();
    |            ^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/wf-unreachable.rs:51:12
    |
@@ -69,5 +97,9 @@ LL |     return;
 LL |     let _: C<'static, 'a, _> = C((), &(), &());
    |            ^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to 8 previous errors
 
diff --git a/src/test/ui/nll/type-check-pointer-coercions.stderr b/src/test/ui/nll/type-check-pointer-coercions.stderr
index 9aa78dfbd4a..9c7601c3a60 100644
--- a/src/test/ui/nll/type-check-pointer-coercions.stderr
+++ b/src/test/ui/nll/type-check-pointer-coercions.stderr
@@ -8,6 +8,10 @@ LL | fn shared_to_const<'a, 'b>(x: &&'a i32) -> *const &'b i32 {
 LL |     x
    |     ^ returning this value requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: lifetime may not live long enough
   --> $DIR/type-check-pointer-coercions.rs:8:5
    |
@@ -18,6 +22,10 @@ LL | fn unique_to_const<'a, 'b>(x: &mut &'a i32) -> *const &'b i32 {
 LL |     x
    |     ^ returning this value requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: lifetime may not live long enough
   --> $DIR/type-check-pointer-coercions.rs:13:5
    |
@@ -40,6 +48,10 @@ LL |     // Two errors because *mut is invariant
 LL |     x
    |     ^ returning this value requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: `'b` and `'a` must be the same; replace one with the other
+
 error: lifetime may not live long enough
   --> $DIR/type-check-pointer-coercions.rs:18:5
    |
@@ -50,6 +62,10 @@ LL | fn mut_to_const<'a, 'b>(x: *mut &'a i32) -> *const &'b i32 {
 LL |     x
    |     ^ returning this value requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: lifetime may not live long enough
   --> $DIR/type-check-pointer-coercions.rs:24:5
    |
@@ -61,6 +77,10 @@ LL | fn array_elem<'a, 'b>(x: &'a i32) -> *const &'b i32 {
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: lifetime may not live long enough
   --> $DIR/type-check-pointer-coercions.rs:30:5
    |
@@ -72,6 +92,10 @@ LL | fn array_coerce<'a, 'b>(x: &'a i32) -> *const [&'b i32; 3] {
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: lifetime may not live long enough
   --> $DIR/type-check-pointer-coercions.rs:36:5
    |
@@ -83,5 +107,9 @@ LL | fn nested_array<'a, 'b>(x: &'a i32) -> *const [&'b i32; 2] {
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: aborting due to 8 previous errors
 
diff --git a/src/test/ui/nll/type-check-pointer-comparisons.stderr b/src/test/ui/nll/type-check-pointer-comparisons.stderr
index c0a994cfb63..5293f48116e 100644
--- a/src/test/ui/nll/type-check-pointer-comparisons.stderr
+++ b/src/test/ui/nll/type-check-pointer-comparisons.stderr
@@ -18,6 +18,10 @@ LL | fn compare_const<'a, 'b>(x: *const &mut &'a i32, y: *const &mut &'b i32) {
 LL |     x == y;
    |          ^ requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: `'b` and `'a` must be the same; replace one with the other
+
 error: lifetime may not live long enough
   --> $DIR/type-check-pointer-comparisons.rs:12:5
    |
@@ -38,6 +42,10 @@ LL | fn compare_mut<'a, 'b>(x: *mut &'a i32, y: *mut &'b i32) {
 LL |     x == y;
    |          ^ requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: `'a` and `'b` must be the same; replace one with the other
+
 error: lifetime may not live long enough
   --> $DIR/type-check-pointer-comparisons.rs:18:5
    |
@@ -58,5 +66,9 @@ LL | fn compare_fn_ptr<'a, 'b, 'c>(f: fn(&'c mut &'a i32), g: fn(&'c mut &'b i32
 LL |     f == g;
    |          ^ requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: `'a` and `'b` must be the same; replace one with the other
+
 error: aborting due to 6 previous errors
 
diff --git a/src/test/ui/nll/user-annotations/closure-substs.stderr b/src/test/ui/nll/user-annotations/closure-substs.stderr
index 384d53f0e4b..ff0cda9114a 100644
--- a/src/test/ui/nll/user-annotations/closure-substs.stderr
+++ b/src/test/ui/nll/user-annotations/closure-substs.stderr
@@ -7,6 +7,10 @@ LL | fn foo<'a>() {
 LL |         return x;
    |                ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/closure-substs.rs:15:16
    |
@@ -24,6 +28,10 @@ LL | fn bar<'a>() {
 LL |         b(x);
    |         ^^^^ argument requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error[E0521]: borrowed data escapes outside of closure
   --> $DIR/closure-substs.rs:29:9
    |
diff --git a/src/test/ui/nll/user-annotations/inherent-associated-constants.stderr b/src/test/ui/nll/user-annotations/inherent-associated-constants.stderr
index 76845469898..48d56fc1c02 100644
--- a/src/test/ui/nll/user-annotations/inherent-associated-constants.stderr
+++ b/src/test/ui/nll/user-annotations/inherent-associated-constants.stderr
@@ -6,5 +6,9 @@ LL | fn non_wf_associated_const<'a>(x: i32) {
 LL |     A::<'a>::IC;
    |     ^^^^^^^^^^^ requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/user-annotations/issue-54124.stderr b/src/test/ui/nll/user-annotations/issue-54124.stderr
index 6cfccf7cb69..64df1264d27 100644
--- a/src/test/ui/nll/user-annotations/issue-54124.stderr
+++ b/src/test/ui/nll/user-annotations/issue-54124.stderr
@@ -16,5 +16,9 @@ LL | fn test<'a>() {
 LL |     let _:fn(&()) = |_:&'a ()| {};
    |                      ^ requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to 2 previous errors
 
diff --git a/src/test/ui/nll/user-annotations/issue-55748-pat-types-constrain-bindings.stderr b/src/test/ui/nll/user-annotations/issue-55748-pat-types-constrain-bindings.stderr
index 5929707e41e..a629cc26763 100644
--- a/src/test/ui/nll/user-annotations/issue-55748-pat-types-constrain-bindings.stderr
+++ b/src/test/ui/nll/user-annotations/issue-55748-pat-types-constrain-bindings.stderr
@@ -7,6 +7,10 @@ LL | fn coupled_regions_lhs<'a>(_x: &'a u32, s: &'static u32) -> &'static u32 {
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/issue-55748-pat-types-constrain-bindings.rs:49:5
    |
@@ -16,6 +20,10 @@ LL | fn coupled_types_lhs<'a>(_x: &'a u32, s: &'static u32) -> &'static u32 {
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/issue-55748-pat-types-constrain-bindings.rs:62:5
    |
@@ -25,5 +33,9 @@ LL | fn coupled_wilds_lhs<'a>(_x: &'a u32, s: &'static u32) -> &'static u32 {
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to 3 previous errors
 
diff --git a/src/test/ui/nll/user-annotations/issue-57731-ascibed-coupled-types.stderr b/src/test/ui/nll/user-annotations/issue-57731-ascibed-coupled-types.stderr
index c99f53c5aa4..937db377573 100644
--- a/src/test/ui/nll/user-annotations/issue-57731-ascibed-coupled-types.stderr
+++ b/src/test/ui/nll/user-annotations/issue-57731-ascibed-coupled-types.stderr
@@ -7,6 +7,10 @@ LL |     let ((y, _z),) = ((s, _x),): (PairCoupledTypes<_>,);
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/issue-57731-ascibed-coupled-types.rs:22:5
    |
@@ -16,6 +20,10 @@ LL |     let ((y, _z),) = ((s, _x),): (PairCoupledRegions<_>,);
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/issue-57731-ascibed-coupled-types.rs:32:5
    |
@@ -25,6 +33,10 @@ LL |     let ((y, _z),) = ((s, _x),) as (PairCoupledTypes<_>,);
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/issue-57731-ascibed-coupled-types.rs:37:5
    |
@@ -34,5 +46,9 @@ LL |     let ((y, _z),) = ((s, _x),) as (PairCoupledRegions<_>,);
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to 4 previous errors
 
diff --git a/src/test/ui/nll/user-annotations/patterns.stderr b/src/test/ui/nll/user-annotations/patterns.stderr
index 7ebd0ae227a..981e59c08c5 100644
--- a/src/test/ui/nll/user-annotations/patterns.stderr
+++ b/src/test/ui/nll/user-annotations/patterns.stderr
@@ -157,6 +157,10 @@ LL | fn static_to_a_to_static_through_variable<'a>(x: &'a u32) -> &'static u32 {
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/patterns.rs:125:5
    |
@@ -166,6 +170,10 @@ LL | fn static_to_a_to_static_through_tuple<'a>(x: &'a u32) -> &'static u32 {
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/patterns.rs:130:5
    |
@@ -175,6 +183,10 @@ LL |     let Single { value: y }: Single<&'a u32> = Single { value: &22 };
 LL |     y
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: lifetime may not live long enough
   --> $DIR/patterns.rs:134:18
    |
@@ -183,6 +195,10 @@ LL | fn a_to_static_then_static<'a>(x: &'a u32) -> &'static u32 {
 LL |     let (y, _z): (&'static u32, u32) = (x, 44);
    |                  ^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error: aborting due to 19 previous errors
 
 Some errors have detailed explanations: E0597, E0716.
diff --git a/src/test/ui/nll/user-annotations/wf-self-type.stderr b/src/test/ui/nll/user-annotations/wf-self-type.stderr
index 8f8e1bc28f6..c3b32865b01 100644
--- a/src/test/ui/nll/user-annotations/wf-self-type.stderr
+++ b/src/test/ui/nll/user-annotations/wf-self-type.stderr
@@ -8,5 +8,9 @@ LL | pub fn foo<'a, 'b>(u: &'b ()) -> &'a () {
 LL |     Foo::xmute(u)
    |     ^^^^^^^^^^^^^ returning this value requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/where_clauses_in_functions.stderr b/src/test/ui/nll/where_clauses_in_functions.stderr
index f3b65ec31ac..c01101cf1f6 100644
--- a/src/test/ui/nll/where_clauses_in_functions.stderr
+++ b/src/test/ui/nll/where_clauses_in_functions.stderr
@@ -8,5 +8,9 @@ LL | fn bar<'a, 'b>(x: &'a u32, y: &'b u32) -> (&'a u32, &'b u32) {
 LL |     foo(x, y)
    |     ^^^^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/nll/where_clauses_in_structs.stderr b/src/test/ui/nll/where_clauses_in_structs.stderr
index e0feb40273f..aa15aa4ddca 100644
--- a/src/test/ui/nll/where_clauses_in_structs.stderr
+++ b/src/test/ui/nll/where_clauses_in_structs.stderr
@@ -8,5 +8,9 @@ LL | fn bar<'a, 'b>(x: Cell<&'a u32>, y: Cell<&'b u32>) {
 LL |     Foo { x, y };
    |           ^ requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/regions/regions-assoc-type-in-supertrait-outlives-container.nll.stderr b/src/test/ui/regions/regions-assoc-type-in-supertrait-outlives-container.nll.stderr
index 4944f2649b7..79ea4caa343 100644
--- a/src/test/ui/regions/regions-assoc-type-in-supertrait-outlives-container.nll.stderr
+++ b/src/test/ui/regions/regions-assoc-type-in-supertrait-outlives-container.nll.stderr
@@ -9,5 +9,9 @@ LL | fn with_assoc<'a,'b>() {
 LL |     let _: &'a WithAssoc<TheType<'b>> = loop { };
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/regions/regions-free-region-ordering-caller.nll.stderr b/src/test/ui/regions/regions-free-region-ordering-caller.nll.stderr
index 16eda2844c6..594ea755e3d 100644
--- a/src/test/ui/regions/regions-free-region-ordering-caller.nll.stderr
+++ b/src/test/ui/regions/regions-free-region-ordering-caller.nll.stderr
@@ -8,6 +8,10 @@ LL | fn call2<'a, 'b>(a: &'a usize, b: &'b usize) {
 LL |     let z: Option<&'b &'a usize> = None;
    |            ^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: lifetime may not live long enough
   --> $DIR/regions-free-region-ordering-caller.rs:17:12
    |
@@ -19,6 +23,10 @@ LL |     let y: Paramd<'a> = Paramd { x: a };
 LL |     let z: Option<&'b Paramd<'a>> = None;
    |            ^^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'b`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'a: 'b`
+
 error: lifetime may not live long enough
   --> $DIR/regions-free-region-ordering-caller.rs:22:12
    |
@@ -29,5 +37,9 @@ LL | fn call4<'a, 'b>(a: &'a usize, b: &'b usize) {
 LL |     let z: Option<&'a &'b usize> = None;
    |            ^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 error: aborting due to 3 previous errors
 
diff --git a/src/test/ui/regions/regions-outlives-projection-container-hrtb.nll.stderr b/src/test/ui/regions/regions-outlives-projection-container-hrtb.nll.stderr
index eed9934be12..fb836d32a2b 100644
--- a/src/test/ui/regions/regions-outlives-projection-container-hrtb.nll.stderr
+++ b/src/test/ui/regions/regions-outlives-projection-container-hrtb.nll.stderr
@@ -9,6 +9,10 @@ LL | fn with_assoc<'a,'b>() {
 LL |     let _: &'a WithHrAssoc<TheType<'b>> = loop { };
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 error: lifetime may not live long enough
   --> $DIR/regions-outlives-projection-container-hrtb.rs:50:12
    |
@@ -20,5 +24,9 @@ LL | fn with_assoc_sub<'a,'b>() {
 LL |     let _: &'a WithHrAssocSub<TheType<'b>> = loop { };
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 error: aborting due to 2 previous errors
 
diff --git a/src/test/ui/regions/regions-outlives-projection-container-wc.nll.stderr b/src/test/ui/regions/regions-outlives-projection-container-wc.nll.stderr
index 8c54d8da0a0..3f5088c12d6 100644
--- a/src/test/ui/regions/regions-outlives-projection-container-wc.nll.stderr
+++ b/src/test/ui/regions/regions-outlives-projection-container-wc.nll.stderr
@@ -9,5 +9,9 @@ LL | fn with_assoc<'a,'b>() {
 LL |     let _: &'a WithAssoc<TheType<'b>> = loop { };
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'b` must outlive `'a`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: add bound `'b: 'a`
+
 error: aborting due to previous error
 
diff --git a/src/test/ui/regions/regions-static-bound.nll.stderr b/src/test/ui/regions/regions-static-bound.nll.stderr
index b5f3e6cfaba..281683ac1da 100644
--- a/src/test/ui/regions/regions-static-bound.nll.stderr
+++ b/src/test/ui/regions/regions-static-bound.nll.stderr
@@ -6,6 +6,10 @@ LL | fn static_id_wrong_way<'a>(t: &'a ()) -> &'static () where 'static: 'a {
 LL |     t
    |     ^ returning this value requires that `'a` must outlive `'static`
 
+help: the following changes may resolve your lifetime errors
+   |
+   = help: replace `'a` with `'static`
+
 error[E0621]: explicit lifetime required in the type of `u`
   --> $DIR/regions-static-bound.rs:14:5
    |