diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 74958c4984962..7414d201f511d 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -593,14 +593,19 @@ impl SourceMap { } pub fn span_to_margin(&self, sp: Span) -> Option { - match self.span_to_prev_source(sp) { - Err(_) => None, - Ok(source) => { - let last_line = source.rsplit_once('\n').unwrap_or(("", &source)).1; + Some(self.indentation_before(sp)?.len()) + } - Some(last_line.len() - last_line.trim_start().len()) - } - } + pub fn indentation_before(&self, sp: Span) -> Option { + self.span_to_source(sp, |src, start_index, _| { + let before = &src[..start_index]; + let last_line = before.rsplit_once('\n').map_or(before, |(_, last)| last); + Ok(last_line + .split_once(|c: char| !c.is_whitespace()) + .map_or(last_line, |(indent, _)| indent) + .to_string()) + }) + .ok() } /// Returns the source snippet as `String` before the given `Span`. diff --git a/compiler/rustc_typeck/src/check/demand.rs b/compiler/rustc_typeck/src/check/demand.rs index 9bbe525914728..ece2d7b4f3793 100644 --- a/compiler/rustc_typeck/src/check/demand.rs +++ b/compiler/rustc_typeck/src/check/demand.rs @@ -199,7 +199,50 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } - let mut compatible_variants = expected_adt + // If the expression is of type () and it's the return expression of a block, + // we suggest adding a separate return expression instead. + // (To avoid things like suggesting `Ok(while .. { .. })`.) + if expr_ty.is_unit() { + if let Some(hir::Node::Block(&hir::Block { + span: block_span, expr: Some(e), .. + })) = self.tcx.hir().find(self.tcx.hir().get_parent_node(expr.hir_id)) + { + if e.hir_id == expr.hir_id { + if let Some(span) = expr.span.find_ancestor_inside(block_span) { + let return_suggestions = + if self.tcx.is_diagnostic_item(sym::Result, expected_adt.did) { + vec!["Ok(())".to_string()] + } else if self.tcx.is_diagnostic_item(sym::Option, expected_adt.did) + { + vec!["None".to_string(), "Some(())".to_string()] + } else { + return; + }; + if let Some(indent) = + self.tcx.sess.source_map().indentation_before(span.shrink_to_lo()) + { + // Add a semicolon, except after `}`. + let semicolon = + match self.tcx.sess.source_map().span_to_snippet(span) { + Ok(s) if s.ends_with('}') => "", + _ => ";", + }; + err.span_suggestions( + span.shrink_to_hi(), + "try adding an expression at the end of the block", + return_suggestions + .into_iter() + .map(|r| format!("{}\n{}{}", semicolon, indent, r)), + Applicability::MaybeIncorrect, + ); + } + return; + } + } + } + } + + let compatible_variants: Vec = expected_adt .variants .iter() .filter(|variant| variant.fields.len() == 1) @@ -220,19 +263,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None } }) - .peekable(); + .collect(); - if compatible_variants.peek().is_some() { - if let Ok(expr_text) = self.tcx.sess.source_map().span_to_snippet(expr.span) { - let suggestions = compatible_variants.map(|v| format!("{}({})", v, expr_text)); - let msg = "try using a variant of the expected enum"; - err.span_suggestions( - expr.span, - msg, - suggestions, - Applicability::MaybeIncorrect, - ); - } + if let [variant] = &compatible_variants[..] { + // Just a single matching variant. + err.multipart_suggestion( + &format!("try wrapping the expression in `{}`", variant), + vec![ + (expr.span.shrink_to_lo(), format!("{}(", variant)), + (expr.span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else if compatible_variants.len() > 1 { + // More than one matching variant. + err.multipart_suggestions( + &format!( + "try wrapping the expression in a variant of `{}`", + self.tcx.def_path_str(expected_adt.did) + ), + compatible_variants.into_iter().map(|variant| { + vec![ + (expr.span.shrink_to_lo(), format!("{}(", variant)), + (expr.span.shrink_to_hi(), ")".to_string()), + ] + }), + Applicability::MaybeIncorrect, + ); } } } diff --git a/src/test/ui/did_you_mean/compatible-variants.rs b/src/test/ui/did_you_mean/compatible-variants.rs new file mode 100644 index 0000000000000..fb6b6a5673d90 --- /dev/null +++ b/src/test/ui/did_you_mean/compatible-variants.rs @@ -0,0 +1,43 @@ +enum Hey { + A(A), + B(B), +} + +fn f() {} + +fn a() -> Option<()> { + while false { + //~^ ERROR mismatched types + f(); + } + //~^ HELP try adding an expression +} + +fn b() -> Result<(), ()> { + f() + //~^ ERROR mismatched types + //~| HELP try adding an expression +} + +fn main() { + let _: Option<()> = while false {}; + //~^ ERROR mismatched types + //~| HELP try wrapping + let _: Option<()> = { + while false {} + //~^ ERROR mismatched types + //~| HELP try adding an expression + }; + let _: Result = 1; + //~^ ERROR mismatched types + //~| HELP try wrapping + let _: Option = 1; + //~^ ERROR mismatched types + //~| HELP try wrapping + let _: Hey = 1; + //~^ ERROR mismatched types + //~| HELP try wrapping + let _: Hey = false; + //~^ ERROR mismatched types + //~| HELP try wrapping +} diff --git a/src/test/ui/did_you_mean/compatible-variants.stderr b/src/test/ui/did_you_mean/compatible-variants.stderr new file mode 100644 index 0000000000000..e77949687fcb2 --- /dev/null +++ b/src/test/ui/did_you_mean/compatible-variants.stderr @@ -0,0 +1,137 @@ +error[E0308]: mismatched types + --> $DIR/compatible-variants.rs:9:5 + | +LL | fn a() -> Option<()> { + | ---------- expected `Option<()>` because of return type +LL | / while false { +LL | | +LL | | f(); +LL | | } + | |_____^ expected enum `Option`, found `()` + | + = note: expected enum `Option<()>` + found unit type `()` +help: try adding an expression at the end of the block + | +LL ~ } +LL + None + | +LL ~ } +LL + Some(()) + | + +error[E0308]: mismatched types + --> $DIR/compatible-variants.rs:17:5 + | +LL | fn b() -> Result<(), ()> { + | -------------- expected `Result<(), ()>` because of return type +LL | f() + | ^^^ expected enum `Result`, found `()` + | + = note: expected enum `Result<(), ()>` + found unit type `()` +help: try adding an expression at the end of the block + | +LL ~ f(); +LL + Ok(()) + | + +error[E0308]: mismatched types + --> $DIR/compatible-variants.rs:23:25 + | +LL | let _: Option<()> = while false {}; + | ---------- ^^^^^^^^^^^^^^ expected enum `Option`, found `()` + | | + | expected due to this + | + = note: expected enum `Option<()>` + found unit type `()` +help: try wrapping the expression in `Some` + | +LL | let _: Option<()> = Some(while false {}); + | +++++ + + +error[E0308]: mismatched types + --> $DIR/compatible-variants.rs:27:9 + | +LL | while false {} + | ^^^^^^^^^^^^^^ expected enum `Option`, found `()` + | + = note: expected enum `Option<()>` + found unit type `()` +help: try adding an expression at the end of the block + | +LL ~ while false {} +LL + None + | +LL ~ while false {} +LL + Some(()) + | + +error[E0308]: mismatched types + --> $DIR/compatible-variants.rs:31:31 + | +LL | let _: Result = 1; + | ---------------- ^ expected enum `Result`, found integer + | | + | expected due to this + | + = note: expected enum `Result` + found type `{integer}` +help: try wrapping the expression in a variant of `Result` + | +LL | let _: Result = Ok(1); + | +++ + +LL | let _: Result = Err(1); + | ++++ + + +error[E0308]: mismatched types + --> $DIR/compatible-variants.rs:34:26 + | +LL | let _: Option = 1; + | ----------- ^ expected enum `Option`, found integer + | | + | expected due to this + | + = note: expected enum `Option` + found type `{integer}` +help: try wrapping the expression in `Some` + | +LL | let _: Option = Some(1); + | +++++ + + +error[E0308]: mismatched types + --> $DIR/compatible-variants.rs:37:28 + | +LL | let _: Hey = 1; + | ------------- ^ expected enum `Hey`, found integer + | | + | expected due to this + | + = note: expected enum `Hey` + found type `{integer}` +help: try wrapping the expression in a variant of `Hey` + | +LL | let _: Hey = Hey::A(1); + | +++++++ + +LL | let _: Hey = Hey::B(1); + | +++++++ + + +error[E0308]: mismatched types + --> $DIR/compatible-variants.rs:40:29 + | +LL | let _: Hey = false; + | -------------- ^^^^^ expected enum `Hey`, found `bool` + | | + | expected due to this + | + = note: expected enum `Hey` + found type `bool` +help: try wrapping the expression in `Hey::B` + | +LL | let _: Hey = Hey::B(false); + | +++++++ + + +error: aborting due to 8 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/did_you_mean/issue-42764.rs b/src/test/ui/did_you_mean/issue-42764.rs index 700f8128a939a..6da640b2b7c76 100644 --- a/src/test/ui/did_you_mean/issue-42764.rs +++ b/src/test/ui/did_you_mean/issue-42764.rs @@ -10,7 +10,7 @@ fn main() { let n: usize = 42; this_function_expects_a_double_option(n); //~^ ERROR mismatched types - //~| HELP try using a variant of the expected enum + //~| HELP try wrapping the expression in a variant of `DoubleOption` } diff --git a/src/test/ui/did_you_mean/issue-42764.stderr b/src/test/ui/did_you_mean/issue-42764.stderr index bc8a93757a599..dbe46704b9320 100644 --- a/src/test/ui/did_you_mean/issue-42764.stderr +++ b/src/test/ui/did_you_mean/issue-42764.stderr @@ -6,12 +6,12 @@ LL | this_function_expects_a_double_option(n); | = note: expected enum `DoubleOption<_>` found type `usize` -help: try using a variant of the expected enum +help: try wrapping the expression in a variant of `DoubleOption` | -LL | this_function_expects_a_double_option(DoubleOption::AlternativeSome(n)); - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LL | this_function_expects_a_double_option(DoubleOption::FirstSome(n)); - | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ++++++++++++++++++++++++ + +LL | this_function_expects_a_double_option(DoubleOption::AlternativeSome(n)); + | ++++++++++++++++++++++++++++++ + error[E0308]: mismatched types --> $DIR/issue-42764.rs:27:33 diff --git a/src/test/ui/fully-qualified-type/fully-qualified-type-name1.stderr b/src/test/ui/fully-qualified-type/fully-qualified-type-name1.stderr index b5018b47b7bf7..03fb299b39cd2 100644 --- a/src/test/ui/fully-qualified-type/fully-qualified-type-name1.stderr +++ b/src/test/ui/fully-qualified-type/fully-qualified-type-name1.stderr @@ -2,13 +2,14 @@ error[E0308]: mismatched types --> $DIR/fully-qualified-type-name1.rs:5:9 | LL | x = 5; - | ^ - | | - | expected enum `Option`, found integer - | help: try using a variant of the expected enum: `Some(5)` + | ^ expected enum `Option`, found integer | = note: expected enum `Option` found type `{integer}` +help: try wrapping the expression in `Some` + | +LL | x = Some(5); + | +++++ + error: aborting due to previous error 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 b9574e3975816..778b13f24cf56 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 @@ -4,13 +4,14 @@ error[E0308]: mismatched types LL | fn bar(x: usize) -> Option { | ------------- expected `Option` because of return type LL | return x; - | ^ - | | - | expected enum `Option`, found `usize` - | help: try using a variant of the expected enum: `Some(x)` + | ^ expected enum `Option`, found `usize` | = note: expected enum `Option` found type `usize` +help: try wrapping the expression in `Some` + | +LL | return Some(x); + | +++++ + error: aborting due to previous error diff --git a/src/test/ui/issues/issue-51632-try-desugar-incompatible-types.stderr b/src/test/ui/issues/issue-51632-try-desugar-incompatible-types.stderr index 0f61e03c3b58f..15d2ef3fce8da 100644 --- a/src/test/ui/issues/issue-51632-try-desugar-incompatible-types.stderr +++ b/src/test/ui/issues/issue-51632-try-desugar-incompatible-types.stderr @@ -12,10 +12,10 @@ help: try removing this `?` LL - missing_discourses()? LL + missing_discourses() | -help: try using a variant of the expected enum +help: try wrapping the expression in `Ok` | LL | Ok(missing_discourses()?) - | + | +++ + error: aborting due to previous error diff --git a/src/test/ui/mismatched_types/abridged.stderr b/src/test/ui/mismatched_types/abridged.stderr index db4e8589291b7..ff1a836c9aec0 100644 --- a/src/test/ui/mismatched_types/abridged.stderr +++ b/src/test/ui/mismatched_types/abridged.stderr @@ -26,13 +26,14 @@ error[E0308]: mismatched types LL | fn b() -> Option { | ----------- expected `Option` because of return type LL | Foo { bar: 1 } - | ^^^^^^^^^^^^^^ - | | - | expected enum `Option`, found struct `Foo` - | help: try using a variant of the expected enum: `Some(Foo { bar: 1 })` + | ^^^^^^^^^^^^^^ expected enum `Option`, found struct `Foo` | = note: expected enum `Option` found struct `Foo` +help: try wrapping the expression in `Some` + | +LL | Some(Foo { bar: 1 }) + | +++++ + error[E0308]: mismatched types --> $DIR/abridged.rs:28:5 @@ -40,13 +41,14 @@ error[E0308]: mismatched types LL | fn c() -> Result { | ---------------- expected `Result` because of return type LL | Foo { bar: 1 } - | ^^^^^^^^^^^^^^ - | | - | expected enum `Result`, found struct `Foo` - | help: try using a variant of the expected enum: `Ok(Foo { bar: 1 })` + | ^^^^^^^^^^^^^^ expected enum `Result`, found struct `Foo` | = note: expected enum `Result` found struct `Foo` +help: try wrapping the expression in `Ok` + | +LL | Ok(Foo { bar: 1 }) + | +++ + error[E0308]: mismatched types --> $DIR/abridged.rs:39:5 diff --git a/src/test/ui/pattern/pat-type-err-let-stmt.stderr b/src/test/ui/pattern/pat-type-err-let-stmt.stderr index 4b4fb08928327..090bd67117eab 100644 --- a/src/test/ui/pattern/pat-type-err-let-stmt.stderr +++ b/src/test/ui/pattern/pat-type-err-let-stmt.stderr @@ -2,14 +2,16 @@ error[E0308]: mismatched types --> $DIR/pat-type-err-let-stmt.rs:6:29 | LL | let Ok(0): Option = 42u8; - | ---------- ^^^^ - | | | - | | expected enum `Option`, found `u8` - | | help: try using a variant of the expected enum: `Some(42u8)` + | ---------- ^^^^ expected enum `Option`, found `u8` + | | | expected due to this | = note: expected enum `Option` found type `u8` +help: try wrapping the expression in `Some` + | +LL | let Ok(0): Option = Some(42u8); + | +++++ + error[E0308]: mismatched types --> $DIR/pat-type-err-let-stmt.rs:6:9 diff --git a/src/test/ui/suggestions/boxed-variant-field.rs b/src/test/ui/suggestions/boxed-variant-field.rs index d8f7fac151356..9b9e70a675fb1 100644 --- a/src/test/ui/suggestions/boxed-variant-field.rs +++ b/src/test/ui/suggestions/boxed-variant-field.rs @@ -9,7 +9,7 @@ fn foo(x: Ty) -> Ty { Ty::List(elem) => foo(elem), //~^ ERROR mismatched types //~| HELP try dereferencing the `Box` - //~| HELP try using a variant of the expected enum + //~| HELP try wrapping } } diff --git a/src/test/ui/suggestions/boxed-variant-field.stderr b/src/test/ui/suggestions/boxed-variant-field.stderr index d4ccb2ca490bc..e865b993a4c17 100644 --- a/src/test/ui/suggestions/boxed-variant-field.stderr +++ b/src/test/ui/suggestions/boxed-variant-field.stderr @@ -10,10 +10,10 @@ help: try dereferencing the `Box` | LL | Ty::List(elem) => foo(*elem), | + -help: try using a variant of the expected enum +help: try wrapping the expression in `Ty::List` | LL | Ty::List(elem) => foo(Ty::List(elem)), - | ~~~~~~~~~~~~~~ + | +++++++++ + error: aborting due to previous error diff --git a/src/test/ui/suggestions/suggest-full-enum-variant-for-local-module.stderr b/src/test/ui/suggestions/suggest-full-enum-variant-for-local-module.stderr index 22a0ce1e91d72..9b6dba7e9e75b 100644 --- a/src/test/ui/suggestions/suggest-full-enum-variant-for-local-module.stderr +++ b/src/test/ui/suggestions/suggest-full-enum-variant-for-local-module.stderr @@ -2,14 +2,16 @@ error[E0308]: mismatched types --> $DIR/suggest-full-enum-variant-for-local-module.rs:9:28 | LL | let _: option::O<()> = (); - | ------------- ^^ - | | | - | | expected enum `O`, found `()` - | | help: try using a variant of the expected enum: `option::O::Some(())` + | ------------- ^^ expected enum `O`, found `()` + | | | expected due to this | = note: expected enum `O<()>` found unit type `()` +help: try wrapping the expression in `option::O::Some` + | +LL | let _: option::O<()> = option::O::Some(()); + | ++++++++++++++++ + error: aborting due to previous error diff --git a/src/test/ui/typeck/issue-46112.stderr b/src/test/ui/typeck/issue-46112.stderr index ec05fbe580ede..39bff88e7f81a 100644 --- a/src/test/ui/typeck/issue-46112.stderr +++ b/src/test/ui/typeck/issue-46112.stderr @@ -2,13 +2,14 @@ error[E0308]: mismatched types --> $DIR/issue-46112.rs:9:21 | LL | fn main() { test(Ok(())); } - | ^^ - | | - | expected enum `Option`, found `()` - | help: try using a variant of the expected enum: `Some(())` + | ^^ expected enum `Option`, found `()` | = note: expected enum `Option<()>` found unit type `()` +help: try wrapping the expression in `Some` + | +LL | fn main() { test(Ok(Some(()))); } + | +++++ + error: aborting due to previous error