diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index c4add4dbdfb2f..433ff3ac902a7 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -907,11 +907,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let ty = self.normalize(expr.span, ty); if self.can_coerce(found, ty) { + let span = self.find_suggestable_ancestor_span(expr); + err.multipart_suggestion( "you might have meant to return this value", vec![ - (expr.span.shrink_to_lo(), "return ".to_string()), - (expr.span.shrink_to_hi(), ";".to_string()), + (span.shrink_to_lo(), "return ".to_string()), + (span.shrink_to_hi(), ";".to_string()), ], Applicability::MaybeIncorrect, ); @@ -919,6 +921,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// Given an expression (possibly as a product of multiple macro expansions in a chain), find + /// the closest ancestor span which is no longer the source of a macro expansion. + fn find_suggestable_ancestor_span(&self, expr: &Expr<'_>) -> Span { + let mut node_id = self.tcx.hir().parent_id(expr.hir_id); + for (id, _) in self.tcx.hir().parent_iter(node_id) { + if self.tcx.hir().span(id).can_be_used_for_suggestions() { + node_id = id; + break; + } + } + let parent = self.tcx.hir().get_enclosing_scope(node_id).unwrap_or(node_id); + let parent_span = self.tcx.hir().span(parent); + let span = expr.span.find_ancestor_inside(parent_span).unwrap_or(expr.span); + span + } + pub(in super::super) fn suggest_missing_parentheses( &self, err: &mut Diagnostic, @@ -1102,10 +1120,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ), )) { + let span = self.find_suggestable_ancestor_span(expr); + let sugg = if expr.precedence().order() >= PREC_POSTFIX { - vec![(expr.span.shrink_to_hi(), ".into()".to_owned())] + vec![(span.shrink_to_hi(), ".into()".to_owned())] } else { - vec![(expr.span.shrink_to_lo(), "(".to_owned()), (expr.span.shrink_to_hi(), ").into()".to_owned())] + vec![(span.shrink_to_lo(), "(".to_owned()), (span.shrink_to_hi(), ").into()".to_owned())] }; diag.multipart_suggestion( format!("call `Into::into` on this expression to convert `{expr_ty}` into `{expected_ty}`"), diff --git a/tests/ui/typeck/issue-110017-format-into-help-deletes-macro.rs b/tests/ui/typeck/issue-110017-format-into-help-deletes-macro.rs new file mode 100644 index 0000000000000..6376d8399ea97 --- /dev/null +++ b/tests/ui/typeck/issue-110017-format-into-help-deletes-macro.rs @@ -0,0 +1,40 @@ +pub fn foo(x: &str) -> Result<(), Box> { + Err(format!("error: {x}")) + //~^ ERROR mismatched types +} + +macro_rules! outer { + ($x: expr) => { + inner!($x) + } +} + +macro_rules! inner { + ($x: expr) => { + format!("error: {}", $x) + //~^ ERROR mismatched types + } +} + +fn bar(x: &str) -> Result<(), Box> { + Err(outer!(x)) +} + +macro_rules! entire_fn_outer { + () => { + entire_fn!(); + } +} + +macro_rules! entire_fn { + () => { + pub fn baz(x: &str) -> Result<(), Box> { + Err(format!("error: {x}")) + //~^ ERROR mismatched types + } + } +} + +entire_fn_outer!(); + +fn main() {} diff --git a/tests/ui/typeck/issue-110017-format-into-help-deletes-macro.stderr b/tests/ui/typeck/issue-110017-format-into-help-deletes-macro.stderr new file mode 100644 index 0000000000000..30df7f1d1c78d --- /dev/null +++ b/tests/ui/typeck/issue-110017-format-into-help-deletes-macro.stderr @@ -0,0 +1,45 @@ +error[E0308]: mismatched types + --> $DIR/issue-110017-format-into-help-deletes-macro.rs:2:9 + | +LL | Err(format!("error: {x}")) + | ^^^^^^^^^^^^^^^^^^^^^ expected `Box`, found `String` + | + = note: expected struct `Box` + found struct `String` + = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) +help: call `Into::into` on this expression to convert `String` into `Box` + | +LL | Err(format!("error: {x}").into()) + | +++++++ + +error[E0308]: mismatched types + --> $DIR/issue-110017-format-into-help-deletes-macro.rs:20:9 + | +LL | Err(outer!(x)) + | ^^^^^^^^^ expected `Box`, found `String` + | + = note: expected struct `Box` + found struct `String` + = note: this error originates in the macro `format` which comes from the expansion of the macro `outer` (in Nightly builds, run with -Z macro-backtrace for more info) +help: call `Into::into` on this expression to convert `String` into `Box` + | +LL | Err(outer!(x).into()) + | +++++++ + +error[E0308]: mismatched types + --> $DIR/issue-110017-format-into-help-deletes-macro.rs:38:1 + | +LL | entire_fn_outer!(); + | ^^^^^^^^^^^^^^^^^^ expected `Box`, found `String` + | + = note: expected struct `Box` + found struct `String` + = note: this error originates in the macro `format` which comes from the expansion of the macro `entire_fn_outer` (in Nightly builds, run with -Z macro-backtrace for more info) +help: call `Into::into` on this expression to convert `String` into `Box` + | +LL | Err(format!("error: {x}").into()) + | +++++++ + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.rs b/tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.rs new file mode 100644 index 0000000000000..f0574ae6bfceb --- /dev/null +++ b/tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.rs @@ -0,0 +1,34 @@ +// https://github.com/rust-lang/rust/issues/112007 +fn bug_report(w: &mut W) -> std::fmt::Result { + if true { + writeln!(w, "`;?` here ->")?; + } else { + writeln!(w, "but not here") + //~^ ERROR mismatched types + } + Ok(()) +} + +macro_rules! baz { + ($w: expr) => { + bar!($w) + } +} + +macro_rules! bar { + ($w: expr) => { + writeln!($w, "but not here") + //~^ ERROR mismatched types + } +} + +fn foo(w: &mut W) -> std::fmt::Result { + if true { + writeln!(w, "`;?` here ->")?; + } else { + baz!(w) + } + Ok(()) +} + +fn main() {} diff --git a/tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.stderr b/tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.stderr new file mode 100644 index 0000000000000..bebcd9a0bba31 --- /dev/null +++ b/tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.stderr @@ -0,0 +1,50 @@ +error[E0308]: mismatched types + --> $DIR/issue-112007-leaked-writeln-macro-internals.rs:6:9 + | +LL | / if true { +LL | | writeln!(w, "`;?` here ->")?; +LL | | } else { +LL | | writeln!(w, "but not here") + | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Result<(), Error>` +LL | | +LL | | } + | |_____- expected this to be `()` + | + = note: expected unit type `()` + found enum `Result<(), std::fmt::Error>` + = note: this error originates in the macro `writeln` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider using a semicolon here + | +LL | }; + | + +help: you might have meant to return this value + | +LL | return writeln!(w, "but not here"); + | ++++++ + + +error[E0308]: mismatched types + --> $DIR/issue-112007-leaked-writeln-macro-internals.rs:29:9 + | +LL | / if true { +LL | | writeln!(w, "`;?` here ->")?; +LL | | } else { +LL | | baz!(w) + | | ^^^^^^^ expected `()`, found `Result<(), Error>` +LL | | } + | |_____- expected this to be `()` + | + = note: expected unit type `()` + found enum `Result<(), std::fmt::Error>` + = note: this error originates in the macro `writeln` which comes from the expansion of the macro `baz` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider using a semicolon here + | +LL | }; + | + +help: you might have meant to return this value + | +LL | return baz!(w); + | ++++++ + + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`.