From 483cff7ed3a3121e6644bbcb9011a762cf871d97 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Nov 2021 17:55:25 +0100 Subject: [PATCH 1/7] Add SourceMap::indentation_before. --- compiler/rustc_span/src/source_map.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) 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`. From 453e2423e6238283acae39007741e0b122fdb191 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Nov 2021 17:56:04 +0100 Subject: [PATCH 2/7] Improve suggestion for unit Option/Result at the end of a block. --- compiler/rustc_typeck/src/check/demand.rs | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/compiler/rustc_typeck/src/check/demand.rs b/compiler/rustc_typeck/src/check/demand.rs index 9bbe525914728..fbbaf9eeef9b5 100644 --- a/compiler/rustc_typeck/src/check/demand.rs +++ b/compiler/rustc_typeck/src/check/demand.rs @@ -199,6 +199,51 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } + // 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.multipart_suggestions( + "try adding an expression at the end of the block", + return_suggestions.into_iter().map(|r| { + vec![( + span.shrink_to_hi(), + format!("{}\n{}{}", semicolon, indent, r), + )] + }), + Applicability::MaybeIncorrect, + ); + } + return; + } + } + } + } + let mut compatible_variants = expected_adt .variants .iter() From b331b6608265f98eea8c3fa85dd67d3156c88ead Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Nov 2021 17:57:03 +0100 Subject: [PATCH 3/7] Improve compatible enum variant suggestions. --- compiler/rustc_typeck/src/check/demand.rs | 40 +++++++++++++++-------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_typeck/src/check/demand.rs b/compiler/rustc_typeck/src/check/demand.rs index fbbaf9eeef9b5..1e43d8fc2bdbf 100644 --- a/compiler/rustc_typeck/src/check/demand.rs +++ b/compiler/rustc_typeck/src/check/demand.rs @@ -244,7 +244,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - let mut compatible_variants = expected_adt + let compatible_variants: Vec = expected_adt .variants .iter() .filter(|variant| variant.fields.len() == 1) @@ -265,19 +265,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, + ); } } } From 48777561ca67abcd259c870f8da28d0062ce6857 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Nov 2021 17:57:30 +0100 Subject: [PATCH 4/7] Update tests. --- src/test/ui/did_you_mean/issue-42764.rs | 2 +- src/test/ui/did_you_mean/issue-42764.stderr | 8 ++++---- .../fully-qualified-type-name1.stderr | 9 +++++---- .../fully-qualified-type-name4.stderr | 9 +++++---- ...51632-try-desugar-incompatible-types.stderr | 4 ++-- src/test/ui/mismatched_types/abridged.stderr | 18 ++++++++++-------- .../ui/pattern/pat-type-err-let-stmt.stderr | 10 ++++++---- ...t-full-enum-variant-for-local-module.stderr | 10 ++++++---- src/test/ui/typeck/issue-46112.stderr | 9 +++++---- 9 files changed, 44 insertions(+), 35 deletions(-) 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/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 From 5a25751c1e5328930743bef8c1f688ed7c69938b Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Nov 2021 17:57:40 +0100 Subject: [PATCH 5/7] Add new tests for compatible variant diagnostics. --- .../ui/did_you_mean/compatible-variants.rs | 43 ++++++ .../did_you_mean/compatible-variants.stderr | 137 ++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 src/test/ui/did_you_mean/compatible-variants.rs create mode 100644 src/test/ui/did_you_mean/compatible-variants.stderr 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`. From 09e4a75f294a9b058bd908c98df38dd41e68f6ff Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Nov 2021 18:12:09 +0100 Subject: [PATCH 6/7] Use span_suggestions instead of multipart_suggestions. --- compiler/rustc_typeck/src/check/demand.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_typeck/src/check/demand.rs b/compiler/rustc_typeck/src/check/demand.rs index 1e43d8fc2bdbf..ece2d7b4f3793 100644 --- a/compiler/rustc_typeck/src/check/demand.rs +++ b/compiler/rustc_typeck/src/check/demand.rs @@ -227,14 +227,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ok(s) if s.ends_with('}') => "", _ => ";", }; - err.multipart_suggestions( + err.span_suggestions( + span.shrink_to_hi(), "try adding an expression at the end of the block", - return_suggestions.into_iter().map(|r| { - vec![( - span.shrink_to_hi(), - format!("{}\n{}{}", semicolon, indent, r), - )] - }), + return_suggestions + .into_iter() + .map(|r| format!("{}\n{}{}", semicolon, indent, r)), Applicability::MaybeIncorrect, ); } From b66fb641da92ea4c9d1c5b0d52ea273cbc21f76e Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Tue, 16 Nov 2021 19:57:12 +0100 Subject: [PATCH 7/7] Update test output. --- src/test/ui/suggestions/boxed-variant-field.rs | 2 +- src/test/ui/suggestions/boxed-variant-field.stderr | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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