diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 238145c5c6ee4..6e007b181f2c2 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -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, @@ -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); @@ -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>, + 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>> { if let ty::Opaque(def_id, substs) = ty.kind() { let future_trait = self.tcx.require_lang_item(LangItem::Future, None); diff --git a/src/test/ui/did_you_mean/compatible-variants-in-pat.rs b/src/test/ui/did_you_mean/compatible-variants-in-pat.rs new file mode 100644 index 0000000000000..09e12dab2d3fc --- /dev/null +++ b/src/test/ui/did_you_mean/compatible-variants-in-pat.rs @@ -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) { + match s { + S => { + //~^ ERROR mismatched types + //~| HELP try wrapping + //~| HELP introduce a new binding instead + } + _ => {} + } +} + +fn c(s: Result) { + match s { + S => { + //~^ ERROR mismatched types + //~| HELP try wrapping + //~| HELP introduce a new binding instead + } + _ => {} + } +} + +fn main() {} diff --git a/src/test/ui/did_you_mean/compatible-variants-in-pat.stderr b/src/test/ui/did_you_mean/compatible-variants-in-pat.stderr new file mode 100644 index 0000000000000..a4c77e08efe19 --- /dev/null +++ b/src/test/ui/did_you_mean/compatible-variants-in-pat.stderr @@ -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` +LL | S => { + | ^ + | | + | expected enum `Option`, found struct `S` + | `S` is interpreted as a unit struct, not a new binding + | + = note: expected enum `Option` + 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` +LL | S => { + | ^ + | | + | expected enum `Result`, found struct `S` + | `S` is interpreted as a unit struct, not a new binding + | + = note: expected enum `Result` + 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`. diff --git a/src/test/ui/issues/issue-12552.stderr b/src/test/ui/issues/issue-12552.stderr index 3d8852ca748af..4b027eba2c25e 100644 --- a/src/test/ui/issues/issue-12552.stderr +++ b/src/test/ui/issues/issue-12552.stderr @@ -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 @@ -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 diff --git a/src/test/ui/issues/issue-3680.stderr b/src/test/ui/issues/issue-3680.stderr index e8fafa76b919b..29ba44f136afa 100644 --- a/src/test/ui/issues/issue-3680.stderr +++ b/src/test/ui/issues/issue-3680.stderr @@ -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 diff --git a/src/test/ui/issues/issue-5358-1.stderr b/src/test/ui/issues/issue-5358-1.stderr index d1bc279c7589a..9d5b8d9d3fc1b 100644 --- a/src/test/ui/issues/issue-5358-1.stderr +++ b/src/test/ui/issues/issue-5358-1.stderr @@ -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` | LL | match S(Either::Left(5)).0 {