about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDylan DPC <dylan.dpc@gmail.com>2021-05-10 16:15:00 +0200
committerGitHub <noreply@github.com>2021-05-10 16:15:00 +0200
commit0740015d59c8c35c803f5f4a0b2aab112e0531aa (patch)
treef62ac654c143a9dbf6cd9c85717d8d567200586e
parent1b30245ea1286df96d673015c4519c861e06977a (diff)
parent2448c7698ef186ead88bd7980f2cac28c55111a8 (diff)
downloadrust-0740015d59c8c35c803f5f4a0b2aab112e0531aa.tar.gz
rust-0740015d59c8c35c803f5f4a0b2aab112e0531aa.zip
Rollup merge of #85050 - FabianWolff:issue-84592, r=jackh726
Fix suggestions for missing return type lifetime specifiers

This pull request aims to fix #84592. The issue is that the current code seems to assume that there is only a single relevant span pointing to the missing lifetime, and only looks at the first one:
https://github.com/rust-lang/rust/blob/e5f83d24aee866a14753a7cedbb4e301dfe5bef5/compiler/rustc_resolve/src/late/lifetimes.rs#L2959

This is incorrect, though, and leads to incorrect error messages and invalid suggestions. For instance, the example from #84592:
```rust
struct TwoLifetimes<'x, 'y> {
    x: &'x (),
    y: &'y (),
}

fn two_lifetimes_needed(a: &(), b: &()) -> TwoLifetimes<'_, '_> {
    TwoLifetimes { x: &(), y: &() }
}
```
currently leads to:
```
error[E0106]: missing lifetime specifiers
 --> src/main.rs:6:57
  |
6 | fn two_lifetimes_needed(a: &(), b: &()) -> TwoLifetimes<'_, '_> {
  |                            ---     ---                  ^^ expected 2 lifetime parameters
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
  |
6 | fn two_lifetimes_needed<'a>(a: &'a (), b: &'a ()) -> TwoLifetimes<'_<'a, 'a>, '_> {
  |                        ^^^^    ^^^^^^     ^^^^^^                  ^^^^^^^^^^
```
There are two problems:
- The error message is wrong. There is only _one_ lifetime parameter expected at the location pointed to by the error message (and another one at a separate location).
- The suggestion is incorrect and will not lead to correct code.

With the changes in this PR, I get the following output:
```
error[E0106]: missing lifetime specifiers
 --> p.rs:6:57
  |
6 | fn two_lifetimes_needed(a: &(), b: &()) -> TwoLifetimes<'_, '_> {
  |                            ---     ---                  ^^  ^^ expected named lifetime parameter
  |                                                         |
  |                                                         expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
  |
6 | fn two_lifetimes_needed<'a>(a: &'a (), b: &'a ()) -> TwoLifetimes<'a, 'a> {
  |                        ^^^^    ^^^^^^     ^^^^^^                  ^^  ^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0106`.
```
Mainly, I changed `add_missing_lifetime_specifiers_label()` to receive a _vector_ of spans (and counts) instead of just one, and adjusted its body accordingly.
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs18
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs334
-rw-r--r--compiler/rustc_resolve/src/late/lifetimes.rs16
-rw-r--r--src/test/ui/suggestions/issue-84592.rs17
-rw-r--r--src/test/ui/suggestions/issue-84592.stderr17
-rw-r--r--src/test/ui/suggestions/missing-lt-for-hrtb.stderr4
-rw-r--r--src/test/ui/suggestions/return-elided-lifetime.rs37
-rw-r--r--src/test/ui/suggestions/return-elided-lifetime.stderr198
8 files changed, 493 insertions, 148 deletions
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index b2f6a0c1014..14ccced2c6a 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -283,6 +283,22 @@ impl Diagnostic {
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self {
+        self.multipart_suggestion_with_style(
+            msg,
+            suggestion,
+            applicability,
+            SuggestionStyle::ShowCode,
+        )
+    }
+
+    /// [`Diagnostic::multipart_suggestion()`] but you can set the [`SuggestionStyle`].
+    pub fn multipart_suggestion_with_style(
+        &mut self,
+        msg: &str,
+        suggestion: Vec<(Span, String)>,
+        applicability: Applicability,
+        style: SuggestionStyle,
+    ) -> &mut Self {
         assert!(!suggestion.is_empty());
         self.suggestions.push(CodeSuggestion {
             substitutions: vec![Substitution {
@@ -292,7 +308,7 @@ impl Diagnostic {
                     .collect(),
             }],
             msg: msg.to_owned(),
-            style: SuggestionStyle::ShowCode,
+            style,
             applicability,
             tool_metadata: Default::default(),
         });
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index 7561b3df3af..fdde687d486 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -9,7 +9,7 @@ use rustc_ast::visit::FnKind;
 use rustc_ast::{self as ast, Expr, ExprKind, Item, ItemKind, NodeId, Path, Ty, TyKind};
 use rustc_ast_pretty::pprust::path_segment_to_string;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder};
+use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, SuggestionStyle};
 use rustc_hir as hir;
 use rustc_hir::def::Namespace::{self, *};
 use rustc_hir::def::{self, CtorKind, CtorOf, DefKind};
@@ -1687,12 +1687,12 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
 impl<'tcx> LifetimeContext<'_, 'tcx> {
     crate fn report_missing_lifetime_specifiers(
         &self,
-        span: Span,
+        spans: Vec<Span>,
         count: usize,
     ) -> DiagnosticBuilder<'tcx> {
         struct_span_err!(
             self.tcx.sess,
-            span,
+            spans,
             E0106,
             "missing lifetime specifier{}",
             pluralize!(count)
@@ -1821,81 +1821,107 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
     crate fn add_missing_lifetime_specifiers_label(
         &self,
         err: &mut DiagnosticBuilder<'_>,
-        span: Span,
-        count: usize,
+        spans_with_counts: Vec<(Span, usize)>,
         lifetime_names: &FxHashSet<Symbol>,
         lifetime_spans: Vec<Span>,
         params: &[ElisionFailureInfo],
     ) {
-        let snippet = self.tcx.sess.source_map().span_to_snippet(span).ok();
-
-        err.span_label(
-            span,
-            &format!(
-                "expected {} lifetime parameter{}",
-                if count == 1 { "named".to_string() } else { count.to_string() },
-                pluralize!(count)
-            ),
-        );
+        let snippets: Vec<Option<String>> = spans_with_counts
+            .iter()
+            .map(|(span, _)| self.tcx.sess.source_map().span_to_snippet(*span).ok())
+            .collect();
 
-        let suggest_existing = |err: &mut DiagnosticBuilder<'_>,
-                                name: &str,
-                                formatter: &dyn Fn(&str) -> String| {
-            if let Some(MissingLifetimeSpot::HigherRanked { span: for_span, span_type }) =
-                self.missing_named_lifetime_spots.iter().rev().next()
-            {
-                // When we have `struct S<'a>(&'a dyn Fn(&X) -> &X);` we want to not only suggest
-                // using `'a`, but also introduce the concept of HRLTs by suggesting
-                // `struct S<'a>(&'a dyn for<'b> Fn(&X) -> &'b X);`. (#72404)
-                let mut introduce_suggestion = vec![];
+        for (span, count) in &spans_with_counts {
+            err.span_label(
+                *span,
+                format!(
+                    "expected {} lifetime parameter{}",
+                    if *count == 1 { "named".to_string() } else { count.to_string() },
+                    pluralize!(*count),
+                ),
+            );
+        }
 
-                let a_to_z_repeat_n = |n| {
-                    (b'a'..=b'z').map(move |c| {
-                        let mut s = '\''.to_string();
-                        s.extend(std::iter::repeat(char::from(c)).take(n));
-                        s
-                    })
-                };
+        let suggest_existing =
+            |err: &mut DiagnosticBuilder<'_>,
+             name: &str,
+             formatters: Vec<Option<Box<dyn Fn(&str) -> String>>>| {
+                if let Some(MissingLifetimeSpot::HigherRanked { span: for_span, span_type }) =
+                    self.missing_named_lifetime_spots.iter().rev().next()
+                {
+                    // When we have `struct S<'a>(&'a dyn Fn(&X) -> &X);` we want to not only suggest
+                    // using `'a`, but also introduce the concept of HRLTs by suggesting
+                    // `struct S<'a>(&'a dyn for<'b> Fn(&X) -> &'b X);`. (#72404)
+                    let mut introduce_suggestion = vec![];
+
+                    let a_to_z_repeat_n = |n| {
+                        (b'a'..=b'z').map(move |c| {
+                            let mut s = '\''.to_string();
+                            s.extend(std::iter::repeat(char::from(c)).take(n));
+                            s
+                        })
+                    };
 
-                // If all single char lifetime names are present, we wrap around and double the chars.
-                let lt_name = (1..)
-                    .flat_map(a_to_z_repeat_n)
-                    .find(|lt| !lifetime_names.contains(&Symbol::intern(&lt)))
-                    .unwrap();
-                let msg = format!(
-                    "consider making the {} lifetime-generic with a new `{}` lifetime",
-                    span_type.descr(),
-                    lt_name,
-                );
-                err.note(
-                    "for more information on higher-ranked polymorphism, visit \
+                    // If all single char lifetime names are present, we wrap around and double the chars.
+                    let lt_name = (1..)
+                        .flat_map(a_to_z_repeat_n)
+                        .find(|lt| !lifetime_names.contains(&Symbol::intern(&lt)))
+                        .unwrap();
+                    let msg = format!(
+                        "consider making the {} lifetime-generic with a new `{}` lifetime",
+                        span_type.descr(),
+                        lt_name,
+                    );
+                    err.note(
+                        "for more information on higher-ranked polymorphism, visit \
                     https://doc.rust-lang.org/nomicon/hrtb.html",
-                );
-                let for_sugg = span_type.suggestion(&lt_name);
-                for param in params {
-                    if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(param.span) {
-                        if snippet.starts_with('&') && !snippet.starts_with("&'") {
-                            introduce_suggestion
-                                .push((param.span, format!("&{} {}", lt_name, &snippet[1..])));
-                        } else if let Some(stripped) = snippet.strip_prefix("&'_ ") {
-                            introduce_suggestion
-                                .push((param.span, format!("&{} {}", lt_name, stripped)));
+                    );
+                    let for_sugg = span_type.suggestion(&lt_name);
+                    for param in params {
+                        if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(param.span)
+                        {
+                            if snippet.starts_with('&') && !snippet.starts_with("&'") {
+                                introduce_suggestion
+                                    .push((param.span, format!("&{} {}", lt_name, &snippet[1..])));
+                            } else if let Some(stripped) = snippet.strip_prefix("&'_ ") {
+                                introduce_suggestion
+                                    .push((param.span, format!("&{} {}", lt_name, stripped)));
+                            }
+                        }
+                    }
+                    introduce_suggestion.push((*for_span, for_sugg));
+                    for ((span, _), formatter) in spans_with_counts.iter().zip(formatters.iter()) {
+                        if let Some(formatter) = formatter {
+                            introduce_suggestion.push((*span, formatter(&lt_name)));
                         }
                     }
+                    err.multipart_suggestion_with_style(
+                        &msg,
+                        introduce_suggestion,
+                        Applicability::MaybeIncorrect,
+                        SuggestionStyle::ShowAlways,
+                    );
                 }
-                introduce_suggestion.push((*for_span, for_sugg));
-                introduce_suggestion.push((span, formatter(&lt_name)));
-                err.multipart_suggestion(&msg, introduce_suggestion, Applicability::MaybeIncorrect);
-            }
 
-            err.span_suggestion_verbose(
-                span,
-                &format!("consider using the `{}` lifetime", lifetime_names.iter().next().unwrap()),
-                formatter(name),
-                Applicability::MaybeIncorrect,
-            );
-        };
-        let suggest_new = |err: &mut DiagnosticBuilder<'_>, sugg: &str| {
+                let spans_suggs: Vec<_> = formatters
+                    .into_iter()
+                    .zip(spans_with_counts.iter())
+                    .filter_map(|(fmt, (span, _))| {
+                        if let Some(formatter) = fmt { Some((formatter, span)) } else { None }
+                    })
+                    .map(|(formatter, span)| (*span, formatter(name)))
+                    .collect();
+                err.multipart_suggestion_with_style(
+                    &format!(
+                        "consider using the `{}` lifetime",
+                        lifetime_names.iter().next().unwrap()
+                    ),
+                    spans_suggs,
+                    Applicability::MaybeIncorrect,
+                    SuggestionStyle::ShowAlways,
+                );
+            };
+        let suggest_new = |err: &mut DiagnosticBuilder<'_>, suggs: Vec<Option<String>>| {
             for missing in self.missing_named_lifetime_spots.iter().rev() {
                 let mut introduce_suggestion = vec![];
                 let msg;
@@ -1940,38 +1966,44 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
                         (*span, span_type.suggestion("'a"))
                     }
                     MissingLifetimeSpot::Static => {
-                        let (span, sugg) = match snippet.as_deref() {
-                            Some("&") => (span.shrink_to_hi(), "'static ".to_owned()),
-                            Some("'_") => (span, "'static".to_owned()),
-                            Some(snippet) if !snippet.ends_with('>') => {
-                                if snippet == "" {
-                                    (
-                                        span,
-                                        std::iter::repeat("'static")
-                                            .take(count)
-                                            .collect::<Vec<_>>()
-                                            .join(", "),
-                                    )
-                                } else {
-                                    (
-                                        span.shrink_to_hi(),
-                                        format!(
-                                            "<{}>",
+                        let mut spans_suggs = Vec::new();
+                        for ((span, count), snippet) in
+                            spans_with_counts.iter().copied().zip(snippets.iter())
+                        {
+                            let (span, sugg) = match snippet.as_deref() {
+                                Some("&") => (span.shrink_to_hi(), "'static ".to_owned()),
+                                Some("'_") => (span, "'static".to_owned()),
+                                Some(snippet) if !snippet.ends_with('>') => {
+                                    if snippet == "" {
+                                        (
+                                            span,
                                             std::iter::repeat("'static")
                                                 .take(count)
                                                 .collect::<Vec<_>>()
-                                                .join(", ")
-                                        ),
-                                    )
+                                                .join(", "),
+                                        )
+                                    } else {
+                                        (
+                                            span.shrink_to_hi(),
+                                            format!(
+                                                "<{}>",
+                                                std::iter::repeat("'static")
+                                                    .take(count)
+                                                    .collect::<Vec<_>>()
+                                                    .join(", ")
+                                            ),
+                                        )
+                                    }
                                 }
-                            }
-                            _ => continue,
-                        };
-                        err.span_suggestion_verbose(
-                            span,
+                                _ => continue,
+                            };
+                            spans_suggs.push((span, sugg.to_string()));
+                        }
+                        err.multipart_suggestion_with_style(
                             "consider using the `'static` lifetime",
-                            sugg.to_string(),
+                            spans_suggs,
                             Applicability::MaybeIncorrect,
+                            SuggestionStyle::ShowAlways,
                         );
                         continue;
                     }
@@ -1986,8 +2018,17 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
                         }
                     }
                 }
-                introduce_suggestion.push((span, sugg.to_string()));
-                err.multipart_suggestion(&msg, introduce_suggestion, Applicability::MaybeIncorrect);
+                for ((span, _), sugg) in spans_with_counts.iter().copied().zip(suggs.iter()) {
+                    if let Some(sugg) = sugg {
+                        introduce_suggestion.push((span, sugg.to_string()));
+                    }
+                }
+                err.multipart_suggestion_with_style(
+                    &msg,
+                    introduce_suggestion,
+                    Applicability::MaybeIncorrect,
+                    SuggestionStyle::ShowAlways,
+                );
                 if should_break {
                     break;
                 }
@@ -1995,68 +2036,75 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
         };
 
         let lifetime_names: Vec<_> = lifetime_names.iter().collect();
-        match (&lifetime_names[..], snippet.as_deref()) {
-            ([name], Some("&")) => {
-                suggest_existing(err, &name.as_str()[..], &|name| format!("&{} ", name));
-            }
-            ([name], Some("'_")) => {
-                suggest_existing(err, &name.as_str()[..], &|n| n.to_string());
-            }
-            ([name], Some("")) => {
-                suggest_existing(err, &name.as_str()[..], &|n| format!("{}, ", n).repeat(count));
-            }
-            ([name], Some(snippet)) if !snippet.ends_with('>') => {
-                let f = |name: &str| {
-                    format!(
-                        "{}<{}>",
-                        snippet,
-                        std::iter::repeat(name.to_string())
-                            .take(count)
-                            .collect::<Vec<_>>()
-                            .join(", ")
-                    )
-                };
-                suggest_existing(err, &name.as_str()[..], &f);
-            }
-            ([], Some("&")) if count == 1 => {
-                suggest_new(err, "&'a ");
-            }
-            ([], Some("'_")) if count == 1 => {
-                suggest_new(err, "'a");
+        match &lifetime_names[..] {
+            [name] => {
+                let mut suggs: Vec<Option<Box<dyn Fn(&str) -> String>>> = Vec::new();
+                for (snippet, (_, count)) in snippets.iter().zip(spans_with_counts.iter().copied())
+                {
+                    suggs.push(match snippet.as_deref() {
+                        Some("&") => Some(Box::new(|name| format!("&{} ", name))),
+                        Some("'_") => Some(Box::new(|n| n.to_string())),
+                        Some("") => Some(Box::new(move |n| format!("{}, ", n).repeat(count))),
+                        Some(snippet) if !snippet.ends_with('>') => Some(Box::new(move |name| {
+                            format!(
+                                "{}<{}>",
+                                snippet,
+                                std::iter::repeat(name.to_string())
+                                    .take(count)
+                                    .collect::<Vec<_>>()
+                                    .join(", ")
+                            )
+                        })),
+                        _ => None,
+                    });
+                }
+                suggest_existing(err, &name.as_str()[..], suggs);
             }
-            ([], Some(snippet)) if !snippet.ends_with('>') => {
-                if snippet == "" {
-                    // This happens when we have `type Bar<'a> = Foo<T>` where we point at the space
-                    // before `T`. We will suggest `type Bar<'a> = Foo<'a, T>`.
-                    suggest_new(
-                        err,
-                        &std::iter::repeat("'a, ").take(count).collect::<Vec<_>>().join(""),
-                    );
-                } else {
-                    suggest_new(
-                        err,
-                        &format!(
+            [] => {
+                let mut suggs = Vec::new();
+                for (snippet, (_, count)) in
+                    snippets.iter().cloned().zip(spans_with_counts.iter().copied())
+                {
+                    suggs.push(match snippet.as_deref() {
+                        Some("&") => Some("&'a ".to_string()),
+                        Some("'_") => Some("'a".to_string()),
+                        Some("") => {
+                            Some(std::iter::repeat("'a, ").take(count).collect::<Vec<_>>().join(""))
+                        }
+                        Some(snippet) => Some(format!(
                             "{}<{}>",
                             snippet,
-                            std::iter::repeat("'a").take(count).collect::<Vec<_>>().join(", ")
-                        ),
-                    );
+                            std::iter::repeat("'a").take(count).collect::<Vec<_>>().join(", "),
+                        )),
+                        None => None,
+                    });
                 }
+                suggest_new(err, suggs);
             }
-            (lts, ..) if lts.len() > 1 => {
+            lts if lts.len() > 1 => {
                 err.span_note(lifetime_spans, "these named lifetimes are available to use");
-                if Some("") == snippet.as_deref() {
+
+                let mut spans_suggs: Vec<_> = Vec::new();
+                for ((span, _), snippet) in spans_with_counts.iter().copied().zip(snippets.iter()) {
+                    match snippet.as_deref() {
+                        Some("") => spans_suggs.push((span, "'lifetime, ".to_string())),
+                        Some("&") => spans_suggs.push((span, "&'lifetime ".to_string())),
+                        _ => {}
+                    }
+                }
+
+                if spans_suggs.len() > 0 {
                     // This happens when we have `Foo<T>` where we point at the space before `T`,
                     // but this can be confusing so we give a suggestion with placeholders.
-                    err.span_suggestion_verbose(
-                        span,
+                    err.multipart_suggestion_with_style(
                         "consider using one of the available lifetimes here",
-                        "'lifetime, ".repeat(count),
+                        spans_suggs,
                         Applicability::HasPlaceholders,
+                        SuggestionStyle::ShowAlways,
                     );
                 }
             }
-            _ => {}
+            _ => unreachable!(),
         }
     }
 
diff --git a/compiler/rustc_resolve/src/late/lifetimes.rs b/compiler/rustc_resolve/src/late/lifetimes.rs
index 174df09cbdb..e8d21af4358 100644
--- a/compiler/rustc_resolve/src/late/lifetimes.rs
+++ b/compiler/rustc_resolve/src/late/lifetimes.rs
@@ -2956,7 +2956,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
             return;
         }
 
-        let span = lifetime_refs[0].span;
         let mut late_depth = 0;
         let mut scope = self.scope;
         let mut lifetime_names = FxHashSet::default();
@@ -3035,7 +3034,16 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
             }
         };
 
-        let mut err = self.report_missing_lifetime_specifiers(span, lifetime_refs.len());
+        let mut spans: Vec<_> = lifetime_refs.iter().map(|lt| lt.span).collect();
+        spans.sort();
+        let mut spans_dedup = spans.clone();
+        spans_dedup.dedup();
+        let spans_with_counts: Vec<_> = spans_dedup
+            .into_iter()
+            .map(|sp| (sp, spans.iter().filter(|nsp| *nsp == &sp).count()))
+            .collect();
+
+        let mut err = self.report_missing_lifetime_specifiers(spans.clone(), lifetime_refs.len());
 
         if let Some(params) = error {
             // If there's no lifetime available, suggest `'static`.
@@ -3043,10 +3051,10 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
                 lifetime_names.insert(kw::StaticLifetime);
             }
         }
+
         self.add_missing_lifetime_specifiers_label(
             &mut err,
-            span,
-            lifetime_refs.len(),
+            spans_with_counts,
             &lifetime_names,
             lifetime_spans,
             error.unwrap_or(&[]),
diff --git a/src/test/ui/suggestions/issue-84592.rs b/src/test/ui/suggestions/issue-84592.rs
new file mode 100644
index 00000000000..aa246aaa3d4
--- /dev/null
+++ b/src/test/ui/suggestions/issue-84592.rs
@@ -0,0 +1,17 @@
+/* Checks whether issue #84592 has been resolved. The issue was
+ * that in this example, there are two expected/missing lifetime
+ * parameters with *different spans*, leading to incorrect
+ * suggestions from rustc.
+ */
+
+struct TwoLifetimes<'x, 'y> {
+    x: &'x (),
+    y: &'y (),
+}
+
+fn two_lifetimes_needed(a: &(), b: &()) -> TwoLifetimes<'_, '_> {
+//~^ ERROR missing lifetime specifiers [E0106]
+    TwoLifetimes { x: &(), y: &() }
+}
+
+fn main() {}
diff --git a/src/test/ui/suggestions/issue-84592.stderr b/src/test/ui/suggestions/issue-84592.stderr
new file mode 100644
index 00000000000..02f9241a6d2
--- /dev/null
+++ b/src/test/ui/suggestions/issue-84592.stderr
@@ -0,0 +1,17 @@
+error[E0106]: missing lifetime specifiers
+  --> $DIR/issue-84592.rs:12:57
+   |
+LL | fn two_lifetimes_needed(a: &(), b: &()) -> TwoLifetimes<'_, '_> {
+   |                            ---     ---                  ^^  ^^ expected named lifetime parameter
+   |                                                         |
+   |                                                         expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
+help: consider introducing a named lifetime parameter
+   |
+LL | fn two_lifetimes_needed<'a>(a: &'a (), b: &'a ()) -> TwoLifetimes<'a, 'a> {
+   |                        ^^^^    ^^^^^^     ^^^^^^                  ^^  ^^
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0106`.
diff --git a/src/test/ui/suggestions/missing-lt-for-hrtb.stderr b/src/test/ui/suggestions/missing-lt-for-hrtb.stderr
index 2cb63500e48..a7a44b511db 100644
--- a/src/test/ui/suggestions/missing-lt-for-hrtb.stderr
+++ b/src/test/ui/suggestions/missing-lt-for-hrtb.stderr
@@ -44,6 +44,10 @@ note: these named lifetimes are available to use
    |
 LL | struct V<'a>(&'a dyn for<'b> Fn(&X) -> &X);
    |          ^^              ^^
+help: consider using one of the available lifetimes here
+   |
+LL | struct V<'a>(&'a dyn for<'b> Fn(&X) -> &'lifetime X);
+   |                                        ^^^^^^^^^^
 
 error[E0106]: missing lifetime specifier
   --> $DIR/missing-lt-for-hrtb.rs:5:41
diff --git a/src/test/ui/suggestions/return-elided-lifetime.rs b/src/test/ui/suggestions/return-elided-lifetime.rs
new file mode 100644
index 00000000000..ca336bbb056
--- /dev/null
+++ b/src/test/ui/suggestions/return-elided-lifetime.rs
@@ -0,0 +1,37 @@
+/* Checks all four scenarios possible in report_elision_failure() of
+ * rustc_resolve::late::lifetimes::LifetimeContext related to returning
+ * borrowed values, in various configurations.
+ */
+
+fn f1() -> &i32 { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+fn f1_() -> (&i32, &i32) { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+//~^^ ERROR missing lifetime specifier [E0106]
+
+fn f2(a: i32, b: i32) -> &i32 { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+fn f2_(a: i32, b: i32) -> (&i32, &i32) { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+//~^^ ERROR missing lifetime specifier [E0106]
+
+struct S<'a, 'b> { a: &'a i32, b: &'b i32 }
+fn f3(s: &S) -> &i32 { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+fn f3_(s: &S, t: &S) -> (&i32, &i32) { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+//~^^ ERROR missing lifetime specifier [E0106]
+
+fn f4<'a, 'b>(a: &'a i32, b: &'b i32) -> &i32 { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+fn f4_<'a, 'b>(a: &'a i32, b: &'b i32) -> (&i32, &i32) { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+//~^^ ERROR missing lifetime specifier [E0106]
+
+fn f5<'a>(a: &'a i32, b: &i32) -> &i32 { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+fn f5_<'a>(a: &'a i32, b: &i32) -> (&i32, &i32) { loop {} }
+//~^ ERROR missing lifetime specifier [E0106]
+//~^^ ERROR missing lifetime specifier [E0106]
+
+fn main() {}
diff --git a/src/test/ui/suggestions/return-elided-lifetime.stderr b/src/test/ui/suggestions/return-elided-lifetime.stderr
new file mode 100644
index 00000000000..888cd5e58ab
--- /dev/null
+++ b/src/test/ui/suggestions/return-elided-lifetime.stderr
@@ -0,0 +1,198 @@
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:6:12
+   |
+LL | fn f1() -> &i32 { loop {} }
+   |            ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
+help: consider using the `'static` lifetime
+   |
+LL | fn f1() -> &'static i32 { loop {} }
+   |            ^^^^^^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:8:14
+   |
+LL | fn f1_() -> (&i32, &i32) { loop {} }
+   |              ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
+help: consider using the `'static` lifetime
+   |
+LL | fn f1_() -> (&'static i32, &i32) { loop {} }
+   |              ^^^^^^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:8:20
+   |
+LL | fn f1_() -> (&i32, &i32) { loop {} }
+   |                    ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
+help: consider using the `'static` lifetime
+   |
+LL | fn f1_() -> (&i32, &'static i32) { loop {} }
+   |                    ^^^^^^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:12:26
+   |
+LL | fn f2(a: i32, b: i32) -> &i32 { loop {} }
+   |                          ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
+help: consider using the `'static` lifetime
+   |
+LL | fn f2(a: i32, b: i32) -> &'static i32 { loop {} }
+   |                          ^^^^^^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:14:28
+   |
+LL | fn f2_(a: i32, b: i32) -> (&i32, &i32) { loop {} }
+   |                            ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
+help: consider using the `'static` lifetime
+   |
+LL | fn f2_(a: i32, b: i32) -> (&'static i32, &i32) { loop {} }
+   |                            ^^^^^^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:14:34
+   |
+LL | fn f2_(a: i32, b: i32) -> (&i32, &i32) { loop {} }
+   |                                  ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
+help: consider using the `'static` lifetime
+   |
+LL | fn f2_(a: i32, b: i32) -> (&i32, &'static i32) { loop {} }
+   |                                  ^^^^^^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:19:17
+   |
+LL | fn f3(s: &S) -> &i32 { loop {} }
+   |          --     ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say which one of `s`'s 3 lifetimes it is borrowed from
+help: consider introducing a named lifetime parameter
+   |
+LL | fn f3<'a>(s: &'a S) -> &'a i32 { loop {} }
+   |      ^^^^    ^^^^^     ^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:21:26
+   |
+LL | fn f3_(s: &S, t: &S) -> (&i32, &i32) { loop {} }
+   |           --     --      ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from one of `s`'s 3 lifetimes or one of `t`'s 3 lifetimes
+help: consider introducing a named lifetime parameter
+   |
+LL | fn f3_<'a>(s: &'a S, t: &'a S) -> (&'a i32, &i32) { loop {} }
+   |       ^^^^    ^^^^^     ^^^^^      ^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:21:32
+   |
+LL | fn f3_(s: &S, t: &S) -> (&i32, &i32) { loop {} }
+   |           --     --            ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from one of `s`'s 3 lifetimes or one of `t`'s 3 lifetimes
+help: consider introducing a named lifetime parameter
+   |
+LL | fn f3_<'a>(s: &'a S, t: &'a S) -> (&i32, &'a i32) { loop {} }
+   |       ^^^^    ^^^^^     ^^^^^            ^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:25:42
+   |
+LL | fn f4<'a, 'b>(a: &'a i32, b: &'b i32) -> &i32 { loop {} }
+   |                  -------     -------     ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
+note: these named lifetimes are available to use
+  --> $DIR/return-elided-lifetime.rs:25:7
+   |
+LL | fn f4<'a, 'b>(a: &'a i32, b: &'b i32) -> &i32 { loop {} }
+   |       ^^  ^^
+help: consider using one of the available lifetimes here
+   |
+LL | fn f4<'a, 'b>(a: &'a i32, b: &'b i32) -> &'lifetime i32 { loop {} }
+   |                                          ^^^^^^^^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:27:44
+   |
+LL | fn f4_<'a, 'b>(a: &'a i32, b: &'b i32) -> (&i32, &i32) { loop {} }
+   |                   -------     -------      ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
+note: these named lifetimes are available to use
+  --> $DIR/return-elided-lifetime.rs:27:8
+   |
+LL | fn f4_<'a, 'b>(a: &'a i32, b: &'b i32) -> (&i32, &i32) { loop {} }
+   |        ^^  ^^
+help: consider using one of the available lifetimes here
+   |
+LL | fn f4_<'a, 'b>(a: &'a i32, b: &'b i32) -> (&'lifetime i32, &i32) { loop {} }
+   |                                            ^^^^^^^^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:27:50
+   |
+LL | fn f4_<'a, 'b>(a: &'a i32, b: &'b i32) -> (&i32, &i32) { loop {} }
+   |                   -------     -------            ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
+note: these named lifetimes are available to use
+  --> $DIR/return-elided-lifetime.rs:27:8
+   |
+LL | fn f4_<'a, 'b>(a: &'a i32, b: &'b i32) -> (&i32, &i32) { loop {} }
+   |        ^^  ^^
+help: consider using one of the available lifetimes here
+   |
+LL | fn f4_<'a, 'b>(a: &'a i32, b: &'b i32) -> (&i32, &'lifetime i32) { loop {} }
+   |                                                  ^^^^^^^^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:31:35
+   |
+LL | fn f5<'a>(a: &'a i32, b: &i32) -> &i32 { loop {} }
+   |              -------     ----     ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
+help: consider using the `'a` lifetime
+   |
+LL | fn f5<'a>(a: &'a i32, b: &i32) -> &'a i32 { loop {} }
+   |                                   ^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:33:37
+   |
+LL | fn f5_<'a>(a: &'a i32, b: &i32) -> (&i32, &i32) { loop {} }
+   |               -------     ----      ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
+help: consider using the `'a` lifetime
+   |
+LL | fn f5_<'a>(a: &'a i32, b: &i32) -> (&'a i32, &i32) { loop {} }
+   |                                     ^^^
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/return-elided-lifetime.rs:33:43
+   |
+LL | fn f5_<'a>(a: &'a i32, b: &i32) -> (&i32, &i32) { loop {} }
+   |               -------     ----            ^ expected named lifetime parameter
+   |
+   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
+help: consider using the `'a` lifetime
+   |
+LL | fn f5_<'a>(a: &'a i32, b: &i32) -> (&i32, &'a i32) { loop {} }
+   |                                           ^^^
+
+error: aborting due to 15 previous errors
+
+For more information about this error, try `rustc --explain E0106`.