Skip to content

Commit

Permalink
Rollup merge of rust-lang#90575 - m-ou-se:compatible-variant-improvem…
Browse files Browse the repository at this point in the history
…ents, r=estebank

Improve suggestions for compatible variants on type mismatch.

Fixes rust-lang#90553.

Before:
![image](https://user-images.githubusercontent.com/783247/140385675-6ff41090-eca2-41bc-b161-99c5dabfec61.png)

After:
![image](https://user-images.githubusercontent.com/783247/140385748-20cf26b5-ea96-4e56-8af2-5fe1ab16fd3b.png)

r? ````@estebank````
  • Loading branch information
matthiaskrgr authored Nov 19, 2021
2 parents 86568e5 + b66fb64 commit 0927113
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 58 deletions.
19 changes: 12 additions & 7 deletions compiler/rustc_span/src/source_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,14 +593,19 @@ impl SourceMap {
}

pub fn span_to_margin(&self, sp: Span) -> Option<usize> {
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<String> {
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`.
Expand Down
83 changes: 70 additions & 13 deletions compiler/rustc_typeck/src/check/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = expected_adt
.variants
.iter()
.filter(|variant| variant.fields.len() == 1)
Expand All @@ -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,
);
}
}
}
Expand Down
43 changes: 43 additions & 0 deletions src/test/ui/did_you_mean/compatible-variants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
enum Hey<A, B> {
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<i32, i32> = 1;
//~^ ERROR mismatched types
//~| HELP try wrapping
let _: Option<i32> = 1;
//~^ ERROR mismatched types
//~| HELP try wrapping
let _: Hey<i32, i32> = 1;
//~^ ERROR mismatched types
//~| HELP try wrapping
let _: Hey<i32, bool> = false;
//~^ ERROR mismatched types
//~| HELP try wrapping
}
137 changes: 137 additions & 0 deletions src/test/ui/did_you_mean/compatible-variants.stderr
Original file line number Diff line number Diff line change
@@ -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<i32, i32> = 1;
| ---------------- ^ expected enum `Result`, found integer
| |
| expected due to this
|
= note: expected enum `Result<i32, i32>`
found type `{integer}`
help: try wrapping the expression in a variant of `Result`
|
LL | let _: Result<i32, i32> = Ok(1);
| +++ +
LL | let _: Result<i32, i32> = Err(1);
| ++++ +

error[E0308]: mismatched types
--> $DIR/compatible-variants.rs:34:26
|
LL | let _: Option<i32> = 1;
| ----------- ^ expected enum `Option`, found integer
| |
| expected due to this
|
= note: expected enum `Option<i32>`
found type `{integer}`
help: try wrapping the expression in `Some`
|
LL | let _: Option<i32> = Some(1);
| +++++ +

error[E0308]: mismatched types
--> $DIR/compatible-variants.rs:37:28
|
LL | let _: Hey<i32, i32> = 1;
| ------------- ^ expected enum `Hey`, found integer
| |
| expected due to this
|
= note: expected enum `Hey<i32, i32>`
found type `{integer}`
help: try wrapping the expression in a variant of `Hey`
|
LL | let _: Hey<i32, i32> = Hey::A(1);
| +++++++ +
LL | let _: Hey<i32, i32> = Hey::B(1);
| +++++++ +

error[E0308]: mismatched types
--> $DIR/compatible-variants.rs:40:29
|
LL | let _: Hey<i32, bool> = false;
| -------------- ^^^^^ expected enum `Hey`, found `bool`
| |
| expected due to this
|
= note: expected enum `Hey<i32, bool>`
found type `bool`
help: try wrapping the expression in `Hey::B`
|
LL | let _: Hey<i32, bool> = Hey::B(false);
| +++++++ +

error: aborting due to 8 previous errors

For more information about this error, try `rustc --explain E0308`.
2 changes: 1 addition & 1 deletion src/test/ui/did_you_mean/issue-42764.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
}


Expand Down
8 changes: 4 additions & 4 deletions src/test/ui/did_you_mean/issue-42764.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize>`
found type `{integer}`
help: try wrapping the expression in `Some`
|
LL | x = Some(5);
| +++++ +

error: aborting due to previous error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ error[E0308]: mismatched types
LL | fn bar(x: usize) -> Option<usize> {
| ------------- expected `Option<usize>` 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<usize>`
found type `usize`
help: try wrapping the expression in `Some`
|
LL | return Some(x);
| +++++ +

error: aborting due to previous error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading

0 comments on commit 0927113

Please sign in to comment.