Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest wrapping patterns in enum variants #95386

Merged
merged 3 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions compiler/rustc_infer/src/infer/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem;
use rustc_hir::{Item, ItemKind, Node};
use rustc_middle::dep_graph::DepContext;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{
self,
error::TypeError,
Expand Down Expand Up @@ -1736,6 +1737,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
};

if should_suggest_fixes {
self.suggest_tuple_pattern(cause, &exp_found, diag);
self.suggest_as_ref_where_appropriate(span, &exp_found, diag);
self.suggest_accessing_field_where_appropriate(cause, &exp_found, diag);
self.suggest_await_on_expect_found(cause, span, &exp_found, diag);
Expand Down Expand Up @@ -1766,6 +1768,73 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
self.note_error_origin(diag, cause, exp_found, terr);
}

fn suggest_tuple_pattern(
&self,
cause: &ObligationCause<'tcx>,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
// Heavily inspired by `FnCtxt::suggest_compatible_variants`, with
// some modifications due to that being in typeck and this being in infer.
if let ObligationCauseCode::Pattern { .. } = cause.code() {
if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() {
let compatible_variants: Vec<_> = expected_adt
.variants()
.iter()
.filter(|variant| {
variant.fields.len() == 1 && variant.ctor_kind == hir::def::CtorKind::Fn
})
.filter_map(|variant| {
let sole_field = &variant.fields[0];
let sole_field_ty = sole_field.ty(self.tcx, substs);
if same_type_modulo_infer(sole_field_ty, exp_found.found) {
let variant_path =
with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
// FIXME #56861: DRYer prelude filtering
if let Some(path) = variant_path.strip_prefix("std::prelude::") {
if let Some((_, path)) = path.split_once("::") {
return Some(path.to_string());
}
}
Some(variant_path)
} else {
None
}
})
.collect();
match &compatible_variants[..] {
[] => {}
[variant] => {
diag.multipart_suggestion_verbose(
&format!("try wrapping the pattern in `{}`", variant),
vec![
(cause.span.shrink_to_lo(), format!("{}(", variant)),
(cause.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MaybeIncorrect,
);
}
_ => {
// More than one matching variant.
diag.multipart_suggestions(
&format!(
"try wrapping the pattern in a variant of `{}`",
self.tcx.def_path_str(expected_adt.did())
),
compatible_variants.into_iter().map(|variant| {
vec![
(cause.span.shrink_to_lo(), format!("{}(", variant)),
(cause.span.shrink_to_hi(), ")".to_string()),
]
}),
Applicability::MaybeIncorrect,
);
}
}
}
}
}

pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option<Binder<'tcx, Ty<'tcx>>> {
if let ty::Opaque(def_id, substs) = ty.kind() {
let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
Expand Down
8 changes: 3 additions & 5 deletions compiler/rustc_typeck/src/check/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr_ty: Ty<'tcx>,
) {
if let ty::Adt(expected_adt, substs) = expected.kind() {
if !expected_adt.is_enum() {
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 .. { .. })`.)
Expand Down Expand Up @@ -336,7 +332,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let compatible_variants: Vec<String> = expected_adt
.variants()
.iter()
.filter(|variant| variant.fields.len() == 1)
.filter(|variant| {
variant.fields.len() == 1 && variant.ctor_kind == hir::def::CtorKind::Fn
})
.filter_map(|variant| {
let sole_field = &variant.fields[0];
let sole_field_ty = sole_field.ty(self.tcx, substs);
Expand Down
41 changes: 41 additions & 0 deletions src/test/ui/did_you_mean/compatible-variants-in-pat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
enum Foo {
Bar(Bar),
}
struct Bar {
x: i32,
}

fn a(f: Foo) {
match f {
Bar { x } => {
//~^ ERROR mismatched types
//~| HELP try wrapping
}
}
}

struct S;

fn b(s: Option<S>) {
match s {
S => {
//~^ ERROR mismatched types
//~| HELP try wrapping
//~| HELP introduce a new binding instead
}
_ => {}
}
}

fn c(s: Result<S, S>) {
match s {
S => {
//~^ ERROR mismatched types
//~| HELP try wrapping
//~| HELP introduce a new binding instead
}
_ => {}
}
}

fn main() {}
68 changes: 68 additions & 0 deletions src/test/ui/did_you_mean/compatible-variants-in-pat.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
error[E0308]: mismatched types
--> $DIR/compatible-variants-in-pat.rs:10:9
|
LL | match f {
| - this expression has type `Foo`
LL | Bar { x } => {
| ^^^^^^^^^ expected enum `Foo`, found struct `Bar`
|
help: try wrapping the pattern in `Foo::Bar`
|
LL | Foo::Bar(Bar { x }) => {
| +++++++++ +

error[E0308]: mismatched types
--> $DIR/compatible-variants-in-pat.rs:21:9
|
LL | struct S;
| --------- unit struct defined here
...
LL | match s {
| - this expression has type `Option<S>`
LL | S => {
| ^
| |
| expected enum `Option`, found struct `S`
| `S` is interpreted as a unit struct, not a new binding
|
= note: expected enum `Option<S>`
found struct `S`
help: try wrapping the pattern in `Some`
|
LL | Some(S) => {
| +++++ +
help: introduce a new binding instead
|
LL | other_s => {
| ~~~~~~~

error[E0308]: mismatched types
--> $DIR/compatible-variants-in-pat.rs:32:9
|
LL | struct S;
| --------- unit struct defined here
...
LL | match s {
| - this expression has type `Result<S, S>`
LL | S => {
| ^
| |
| expected enum `Result`, found struct `S`
| `S` is interpreted as a unit struct, not a new binding
|
= note: expected enum `Result<S, S>`
found struct `S`
help: try wrapping the pattern in a variant of `Result`
|
LL | Ok(S) => {
| +++ +
LL | Err(S) => {
| ++++ +
help: introduce a new binding instead
|
LL | other_s => {
| ~~~~~~~

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0308`.
24 changes: 24 additions & 0 deletions src/test/ui/did_you_mean/compatible-variants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,27 @@ fn main() {
//~^ ERROR mismatched types
//~| HELP try wrapping
}

enum A {
B { b: B},
}

struct A2(B);

enum B {
Fst,
Snd,
}

fn foo() {
// We don't want to suggest `A::B(B::Fst)` here.
let a: A = B::Fst;
//~^ ERROR mismatched types
}

fn bar() {
// But we _do_ want to suggest `A2(B::Fst)` here!
let a: A2 = B::Fst;
//~^ ERROR mismatched types
//~| HELP try wrapping
}
23 changes: 22 additions & 1 deletion src/test/ui/did_you_mean/compatible-variants.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ help: try wrapping the expression in `Some`
LL | let _ = Foo { bar: Some(bar) };
| ++++++++++ +

error: aborting due to 11 previous errors
error[E0308]: mismatched types
--> $DIR/compatible-variants.rs:81:16
|
LL | let a: A = B::Fst;
| - ^^^^^^ expected enum `A`, found enum `B`
| |
| expected due to this

error[E0308]: mismatched types
--> $DIR/compatible-variants.rs:87:17
|
LL | let a: A2 = B::Fst;
| -- ^^^^^^ expected struct `A2`, found enum `B`
| |
| expected due to this
|
help: try wrapping the expression in `A2`
|
LL | let a: A2 = A2(B::Fst);
| +++ +

error: aborting due to 13 previous errors

For more information about this error, try `rustc --explain E0308`.
8 changes: 8 additions & 0 deletions src/test/ui/issues/issue-12552.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ LL | Some(k) => match k {
|
= note: expected enum `Result<_, {integer}>`
found enum `Option<_>`
help: try wrapping the pattern in `Ok`
|
LL | Ok(Some(k)) => match k {
| +++ +

error[E0308]: mismatched types
--> $DIR/issue-12552.rs:9:5
Expand All @@ -20,6 +24,10 @@ LL | None => ()
|
= note: expected enum `Result<_, {integer}>`
found enum `Option<_>`
help: try wrapping the pattern in `Ok`
|
LL | Ok(None) => ()
| +++ +

error: aborting due to 2 previous errors

Expand Down
4 changes: 4 additions & 0 deletions src/test/ui/issues/issue-3680.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ LL | Err(_) => ()
|
= note: expected enum `Option<_>`
found enum `Result<_, _>`
help: try wrapping the pattern in `Some`
|
LL | Some(Err(_)) => ()
| +++++ +

error: aborting due to previous error

Expand Down
4 changes: 4 additions & 0 deletions src/test/ui/issues/issue-5358-1.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ LL | Either::Right(_) => {}
|
= note: expected struct `S`
found enum `Either<_, _>`
help: try wrapping the pattern in `S`
|
LL | S(Either::Right(_)) => {}
| ++ +
help: you might have meant to use field `0` whose type is `Either<usize, usize>`
|
LL | match S(Either::Left(5)).0 {
Expand Down