Rollup merge of #57723 - estebank:fix, r=davidtwco

Point at cause for expectation in return type type error

Various improvements and fixes for type errors in return expressions.

Fix #57664.
diff --git a/src/librustc_typeck/check/coercion.rs b/src/librustc_typeck/check/coercion.rs
index a82a0d3..dd63b4f 100644
--- a/src/librustc_typeck/check/coercion.rs
+++ b/src/librustc_typeck/check/coercion.rs
@@ -1216,7 +1216,7 @@
                                       "supposed to be part of a block tail expression, but the \
                                        expression is empty");
                         });
-                        fcx.suggest_mismatched_types_on_tail(
+                        let pointing_at_return_type = fcx.suggest_mismatched_types_on_tail(
                             &mut db,
                             expr,
                             expected,
@@ -1224,7 +1224,44 @@
                             cause.span,
                             blk_id,
                         );
-                        if let Some(sp) = fcx.ret_coercion_span.borrow().as_ref() {
+                        // FIXME: replace with navigating up the chain until hitting an fn or
+                        // bailing if no "pass-through" Node is found, in order to provide a
+                        // suggestion when encountering something like:
+                        // ```
+                        // fn foo(a: bool) -> impl Debug {
+                        //     if a {
+                        //         bar()?;
+                        //     }
+                        //     {
+                        //         let x = unsafe { bar() };
+                        //         x
+                        //     }
+                        // }
+                        // ```
+                        //
+                        // Verify that this is a tail expression of a function, otherwise the
+                        // label pointing out the cause for the type coercion will be wrong
+                        // as prior return coercions would not be relevant (#57664).
+                        let parent_id = fcx.tcx.hir().get_parent_node(blk_id);
+                        let parent = fcx.tcx.hir().get(fcx.tcx.hir().get_parent_node(parent_id));
+                        if fcx.get_node_fn_decl(parent).is_some() && !pointing_at_return_type {
+                            if let Some(sp) = fcx.ret_coercion_span.borrow().as_ref() {
+                                db.span_label(*sp, reason_label);
+                            }
+                        }
+                    }
+                    ObligationCauseCode::ReturnType(_id) => {
+                        db = fcx.report_mismatched_types(cause, expected, found, err);
+                        let _id = fcx.tcx.hir().get_parent_node(_id);
+                        let mut pointing_at_return_type = false;
+                        if let Some((fn_decl, can_suggest)) = fcx.get_fn_decl(_id) {
+                            pointing_at_return_type = fcx.suggest_missing_return_type(
+                                &mut db, &fn_decl, expected, found, can_suggest);
+                        }
+                        if let (Some(sp), false) = (
+                            fcx.ret_coercion_span.borrow().as_ref(),
+                            pointing_at_return_type,
+                        ) {
                             if !sp.overlaps(cause.span) {
                                 db.span_label(*sp, reason_label);
                             }
@@ -1232,11 +1269,6 @@
                     }
                     _ => {
                         db = fcx.report_mismatched_types(cause, expected, found, err);
-                        if let Some(sp) = fcx.ret_coercion_span.borrow().as_ref() {
-                            if !sp.overlaps(cause.span) {
-                                db.span_label(*sp, reason_label);
-                            }
-                        }
                     }
                 }
 
diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs
index 4957a12..c9173df 100644
--- a/src/librustc_typeck/check/mod.rs
+++ b/src/librustc_typeck/check/mod.rs
@@ -4347,11 +4347,15 @@
                     struct_span_err!(self.tcx.sess, expr.span, E0572,
                                      "return statement outside of function body").emit();
                 } else if let Some(ref e) = *expr_opt {
-                    *self.ret_coercion_span.borrow_mut() = Some(e.span);
+                    if self.ret_coercion_span.borrow().is_none() {
+                        *self.ret_coercion_span.borrow_mut() = Some(e.span);
+                    }
                     self.check_return_expr(e);
                 } else {
                     let mut coercion = self.ret_coercion.as_ref().unwrap().borrow_mut();
-                    *self.ret_coercion_span.borrow_mut() = Some(expr.span);
+                    if self.ret_coercion_span.borrow().is_none() {
+                        *self.ret_coercion_span.borrow_mut() = Some(expr.span);
+                    }
                     let cause = self.cause(expr.span, ObligationCauseCode::ReturnNoExpression);
                     if let Some((fn_decl, _)) = self.get_fn_decl(expr.id) {
                         coercion.coerce_forced_unit(
@@ -5081,12 +5085,15 @@
         found: Ty<'tcx>,
         cause_span: Span,
         blk_id: ast::NodeId,
-    ) {
+    ) -> bool {
         self.suggest_missing_semicolon(err, expression, expected, cause_span);
+        let mut pointing_at_return_type = false;
         if let Some((fn_decl, can_suggest)) = self.get_fn_decl(blk_id) {
-            self.suggest_missing_return_type(err, &fn_decl, expected, found, can_suggest);
+            pointing_at_return_type = self.suggest_missing_return_type(
+                err, &fn_decl, expected, found, can_suggest);
         }
         self.suggest_ref_or_into(err, expression, expected, found);
+        pointing_at_return_type
     }
 
     pub fn suggest_ref_or_into(
@@ -5185,12 +5192,14 @@
     /// This routine checks if the return type is left as default, the method is not part of an
     /// `impl` block and that it isn't the `main` method. If so, it suggests setting the return
     /// type.
-    fn suggest_missing_return_type(&self,
-                                   err: &mut DiagnosticBuilder<'tcx>,
-                                   fn_decl: &hir::FnDecl,
-                                   expected: Ty<'tcx>,
-                                   found: Ty<'tcx>,
-                                   can_suggest: bool) {
+    fn suggest_missing_return_type(
+        &self,
+        err: &mut DiagnosticBuilder<'tcx>,
+        fn_decl: &hir::FnDecl,
+        expected: Ty<'tcx>,
+        found: Ty<'tcx>,
+        can_suggest: bool,
+    ) -> bool {
         // Only suggest changing the return type for methods that
         // haven't set a return type at all (and aren't `fn main()` or an impl).
         match (&fn_decl.output, found.is_suggestable(), can_suggest, expected.is_unit()) {
@@ -5200,16 +5209,19 @@
                     "try adding a return type",
                     format!("-> {} ", self.resolve_type_vars_with_obligations(found)),
                     Applicability::MachineApplicable);
+                true
             }
             (&hir::FunctionRetTy::DefaultReturn(span), false, true, true) => {
                 err.span_label(span, "possibly return type missing here?");
+                true
             }
             (&hir::FunctionRetTy::DefaultReturn(span), _, false, true) => {
                 // `fn main()` must return `()`, do not suggest changing return type
                 err.span_label(span, "expected `()` because of default return type");
+                true
             }
             // expectation was caused by something else, not the default return
-            (&hir::FunctionRetTy::DefaultReturn(_), _, _, false) => {}
+            (&hir::FunctionRetTy::DefaultReturn(_), _, _, false) => false,
             (&hir::FunctionRetTy::Return(ref ty), _, _, _) => {
                 // Only point to return type if the expected type is the return type, as if they
                 // are not, the expectation must have been caused by something else.
@@ -5221,7 +5233,9 @@
                 if ty.sty == expected.sty {
                     err.span_label(sp, format!("expected `{}` because of return type",
                                                expected));
+                    return true;
                 }
+                false
             }
         }
     }
diff --git a/src/test/ui/fully-qualified-type/fully-qualified-type-name2.stderr b/src/test/ui/fully-qualified-type/fully-qualified-type-name2.stderr
index 9a33d29..47bb5e4 100644
--- a/src/test/ui/fully-qualified-type/fully-qualified-type-name2.stderr
+++ b/src/test/ui/fully-qualified-type/fully-qualified-type-name2.stderr
@@ -1,6 +1,8 @@
 error[E0308]: mismatched types
   --> $DIR/fully-qualified-type-name2.rs:12:12
    |
+LL | fn bar(x: x::Foo) -> y::Foo {
+   |                      ------ expected `y::Foo` because of return type
 LL |     return x;
    |            ^ expected enum `y::Foo`, found enum `x::Foo`
    |
diff --git a/src/test/ui/fully-qualified-type/fully-qualified-type-name4.stderr b/src/test/ui/fully-qualified-type/fully-qualified-type-name4.stderr
index f03aaa6..b341879 100644
--- a/src/test/ui/fully-qualified-type/fully-qualified-type-name4.stderr
+++ b/src/test/ui/fully-qualified-type/fully-qualified-type-name4.stderr
@@ -1,6 +1,8 @@
 error[E0308]: mismatched types
   --> $DIR/fully-qualified-type-name4.rs:6:12
    |
+LL | fn bar(x: usize) -> Option<usize> {
+   |                     ------------- expected `std::option::Option<usize>` because of return type
 LL |     return x;
    |            ^ expected enum `std::option::Option`, found usize
    |
diff --git a/src/test/ui/liveness/liveness-forgot-ret.stderr b/src/test/ui/liveness/liveness-forgot-ret.stderr
index bbcbbdb..a970b80 100644
--- a/src/test/ui/liveness/liveness-forgot-ret.stderr
+++ b/src/test/ui/liveness/liveness-forgot-ret.stderr
@@ -2,7 +2,7 @@
   --> $DIR/liveness-forgot-ret.rs:3:19
    |
 LL | fn f(a: isize) -> isize { if god_exists(a) { return 5; }; }
-   |    -              ^^^^^ expected isize, found ()    - expected because of this statement
+   |    -              ^^^^^ expected isize, found ()
    |    |
    |    this function's body doesn't return
    |
diff --git a/src/test/ui/point-to-type-err-cause-on-impl-trait-return-2.rs b/src/test/ui/point-to-type-err-cause-on-impl-trait-return-2.rs
new file mode 100644
index 0000000..50f1fe8
--- /dev/null
+++ b/src/test/ui/point-to-type-err-cause-on-impl-trait-return-2.rs
@@ -0,0 +1,17 @@
+fn unrelated() -> Result<(), std::string::ParseError> {  // #57664
+    let x = 0;
+
+    match x {
+        1 => {
+            let property_value_as_string = "a".parse()?;
+        }
+        2 => {
+            let value: &bool = unsafe { &42 };
+            //~^ ERROR mismatched types
+        }
+    };
+
+    Ok(())
+}
+
+fn main() {}
diff --git a/src/test/ui/point-to-type-err-cause-on-impl-trait-return-2.stderr b/src/test/ui/point-to-type-err-cause-on-impl-trait-return-2.stderr
new file mode 100644
index 0000000..edaa60e
--- /dev/null
+++ b/src/test/ui/point-to-type-err-cause-on-impl-trait-return-2.stderr
@@ -0,0 +1,12 @@
+error[E0308]: mismatched types
+  --> $DIR/point-to-type-err-cause-on-impl-trait-return-2.rs:9:41
+   |
+LL |             let value: &bool = unsafe { &42 };
+   |                                         ^^^ expected bool, found integer
+   |
+   = note: expected type `&bool`
+              found type `&{integer}`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/point-to-type-err-cause-on-impl-trait-return.rs b/src/test/ui/point-to-type-err-cause-on-impl-trait-return.rs
new file mode 100644
index 0000000..95b4036
--- /dev/null
+++ b/src/test/ui/point-to-type-err-cause-on-impl-trait-return.rs
@@ -0,0 +1,36 @@
+fn foo() -> impl std::fmt::Display {
+    if false {
+        return 0i32;
+    }
+    1u32
+    //~^ ERROR mismatched types
+}
+
+fn bar() -> impl std::fmt::Display {
+    if false {
+        return 0i32;
+    } else {
+        return 1u32;
+        //~^ ERROR mismatched types
+    }
+}
+
+fn baz() -> impl std::fmt::Display {
+    if false {
+    //~^ ERROR mismatched types
+        return 0i32;
+    } else {
+        1u32
+    }
+}
+
+fn qux() -> impl std::fmt::Display {
+    if false {
+        0i32
+    } else {
+        1u32
+        //~^ ERROR if and else have incompatible types
+    }
+}
+
+fn main() {}
diff --git a/src/test/ui/point-to-type-err-cause-on-impl-trait-return.stderr b/src/test/ui/point-to-type-err-cause-on-impl-trait-return.stderr
new file mode 100644
index 0000000..62da078
--- /dev/null
+++ b/src/test/ui/point-to-type-err-cause-on-impl-trait-return.stderr
@@ -0,0 +1,58 @@
+error[E0308]: mismatched types
+  --> $DIR/point-to-type-err-cause-on-impl-trait-return.rs:5:5
+   |
+LL |         return 0i32;
+   |                ---- expected because of this statement
+LL |     }
+LL |     1u32
+   |     ^^^^ expected i32, found u32
+   |
+   = note: expected type `i32`
+              found type `u32`
+
+error[E0308]: mismatched types
+  --> $DIR/point-to-type-err-cause-on-impl-trait-return.rs:13:16
+   |
+LL |         return 0i32;
+   |                ---- expected because of this statement
+LL |     } else {
+LL |         return 1u32;
+   |                ^^^^ expected i32, found u32
+   |
+   = note: expected type `i32`
+              found type `u32`
+
+error[E0308]: mismatched types
+  --> $DIR/point-to-type-err-cause-on-impl-trait-return.rs:19:5
+   |
+LL | /     if false {
+LL | |     //~^ ERROR mismatched types
+LL | |         return 0i32;
+   | |                ---- expected because of this statement
+LL | |     } else {
+LL | |         1u32
+LL | |     }
+   | |_____^ expected i32, found u32
+   |
+   = note: expected type `i32`
+              found type `u32`
+
+error[E0308]: if and else have incompatible types
+  --> $DIR/point-to-type-err-cause-on-impl-trait-return.rs:31:9
+   |
+LL | /     if false {
+LL | |         0i32
+   | |         ---- expected because of this
+LL | |     } else {
+LL | |         1u32
+   | |         ^^^^ expected i32, found u32
+LL | |         //~^ ERROR if and else have incompatible types
+LL | |     }
+   | |_____- if and else have incompatible types
+   |
+   = note: expected type `i32`
+              found type `u32`
+
+error: aborting due to 4 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/proc-macro/span-preservation.stderr b/src/test/ui/proc-macro/span-preservation.stderr
index 1f9103a..db52490 100644
--- a/src/test/ui/proc-macro/span-preservation.stderr
+++ b/src/test/ui/proc-macro/span-preservation.stderr
@@ -15,6 +15,9 @@
 error[E0308]: mismatched types
   --> $DIR/span-preservation.rs:19:29
    |
+LL | fn b(x: Option<isize>) -> usize {
+   |                           ----- expected `usize` because of return type
+LL |     match x {
 LL |         Some(x) => { return x }, //~ ERROR mismatched types
    |                             ^ expected usize, found isize
 
diff --git a/src/test/ui/return/return-from-diverging.stderr b/src/test/ui/return/return-from-diverging.stderr
index c84dd19..2862ae6 100644
--- a/src/test/ui/return/return-from-diverging.stderr
+++ b/src/test/ui/return/return-from-diverging.stderr
@@ -1,6 +1,8 @@
 error[E0308]: mismatched types
   --> $DIR/return-from-diverging.rs:4:12
    |
+LL | fn fail() -> ! {
+   |              - expected `!` because of return type
 LL |     return "wow"; //~ ERROR mismatched types
    |            ^^^^^ expected !, found reference
    |
diff --git a/src/test/ui/tail-typeck.stderr b/src/test/ui/tail-typeck.stderr
index eadf3d6..1170f5c 100644
--- a/src/test/ui/tail-typeck.stderr
+++ b/src/test/ui/tail-typeck.stderr
@@ -2,7 +2,9 @@
   --> $DIR/tail-typeck.rs:3:26
    |
 LL | fn f() -> isize { return g(); }
-   |                          ^^^ expected isize, found usize
+   |           -----          ^^^ expected isize, found usize
+   |           |
+   |           expected `isize` because of return type
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/wrong-ret-type.stderr b/src/test/ui/wrong-ret-type.stderr
index 221806f..cf59f42 100644
--- a/src/test/ui/wrong-ret-type.stderr
+++ b/src/test/ui/wrong-ret-type.stderr
@@ -2,7 +2,9 @@
   --> $DIR/wrong-ret-type.rs:2:49
    |
 LL | fn mk_int() -> usize { let i: isize = 3; return i; }
-   |                                                 ^ expected usize, found isize
+   |                -----                            ^ expected usize, found isize
+   |                |
+   |                expected `usize` because of return type
 
 error: aborting due to previous error