From b791fd78694c4b822c40435f2e0ff56fc3fff071 Mon Sep 17 00:00:00 2001 From: Maximilian Azendorf Date: Sun, 7 Jun 2026 17:11:53 +0200 Subject: [PATCH] Fix diagnostics for non-exhaustive destructuring assignments --- compiler/rustc_mir_build/src/errors.rs | 8 +++-- .../src/thir/pattern/check_match.rs | 29 +++++++++++++++++-- .../non-exhaustive-destructure.rs | 2 +- .../non-exhaustive-destructure.stderr | 8 ++--- .../refutable-enum-assignment.rs | 11 +++++++ .../refutable-enum-assignment.stderr | 21 ++++++++++++++ 6 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 tests/ui/destructuring-assignment/refutable-enum-assignment.rs create mode 100644 tests/ui/destructuring-assignment/refutable-enum-assignment.stderr diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index b4dac5b244f10..3752fc3448ba4 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -1241,12 +1241,14 @@ pub(crate) struct PatternNotCovered<'s, 'tcx> { pub(crate) misc_suggestion: Option, } -#[derive(Subdiagnostic)] +#[derive(Subdiagnostic, Debug)] #[note( - "`let` bindings require an \"irrefutable pattern\", like a `struct` or an `enum` with only one variant" + "{$descr} require an \"irrefutable pattern\", like a `struct` or an `enum` with only one variant" )] #[note("for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html")] -pub(crate) struct Inform; +pub(crate) struct Inform { + pub(crate) descr: &'static str, +} #[derive(Subdiagnostic)] #[label( diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index b4c340cfee2c0..8e147a44d3b75 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -60,7 +60,7 @@ pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), Err for param in thir.params.iter() { if let Some(ref pattern) = param.pat { - visitor.check_binding_is_irrefutable(pattern, origin, None, None); + visitor.check_binding_is_irrefutable(pattern, origin, None, None, None); } } visitor.error @@ -436,7 +436,29 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { assert!(self.let_source != LetSource::None); let scrut = scrutinee.map(|id| &self.thir[id]); if let LetSource::PlainLet = self.let_source { - self.check_binding_is_irrefutable(pat, "local binding", scrut, Some(span)); + // `lhs = rhs` destructuring assignments are lowered to a `let` tagged + // `AssignDesugar`; report them as assignments, not `let` bindings (#157553). + if let hir::Node::LetStmt(&hir::LetStmt { + source: hir::LocalSource::AssignDesugar, + .. + }) = self.tcx.hir_node(self.hir_source) + { + self.check_binding_is_irrefutable( + pat, + "assignment", + Some(Inform { descr: "destructuring assignments" }), + scrut, + None, + ); + } else { + self.check_binding_is_irrefutable( + pat, + "local binding", + Some(Inform { descr: "`let` bindings" }), + scrut, + Some(span), + ); + } } else if let Ok(Irrefutable) = self.is_let_irrefutable(pat, scrut) { if span.from_expansion() { self.lint_single_let(span, None, None); @@ -541,6 +563,7 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { "`for` loop binding", None, None, + None, ); } else { // span after scrutinee, or after `.match`. That is, the braces, arms, @@ -645,6 +668,7 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { &mut self, pat: &'p Pat<'tcx>, origin: &str, + inform: Option, scrut: Option<&Expr<'tcx>>, sp: Option, ) { @@ -657,7 +681,6 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { return; } - let inform = sp.is_some().then_some(Inform); let mut let_suggestion = None; let mut misc_suggestion = None; let mut interpreted_as_const = None; diff --git a/tests/ui/destructuring-assignment/non-exhaustive-destructure.rs b/tests/ui/destructuring-assignment/non-exhaustive-destructure.rs index 39939f2bad634..d1bcec7c9e032 100644 --- a/tests/ui/destructuring-assignment/non-exhaustive-destructure.rs +++ b/tests/ui/destructuring-assignment/non-exhaustive-destructure.rs @@ -1,4 +1,4 @@ fn main() { None = Some(3); - //~^ ERROR refutable pattern in local binding + //~^ ERROR refutable pattern in assignment } diff --git a/tests/ui/destructuring-assignment/non-exhaustive-destructure.stderr b/tests/ui/destructuring-assignment/non-exhaustive-destructure.stderr index 88f7d2da47c28..a98a494c34c99 100644 --- a/tests/ui/destructuring-assignment/non-exhaustive-destructure.stderr +++ b/tests/ui/destructuring-assignment/non-exhaustive-destructure.stderr @@ -1,16 +1,12 @@ -error[E0005]: refutable pattern in local binding +error[E0005]: refutable pattern in assignment --> $DIR/non-exhaustive-destructure.rs:2:5 | LL | None = Some(3); | ^^^^ pattern `Some(_)` not covered | - = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant + = note: destructuring assignments require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Option` -help: you might want to use `if let` to ignore the variant that isn't matched - | -LL | if None = Some(3) { todo!() }; - | ++ +++++++++++ error: aborting due to 1 previous error diff --git a/tests/ui/destructuring-assignment/refutable-enum-assignment.rs b/tests/ui/destructuring-assignment/refutable-enum-assignment.rs new file mode 100644 index 0000000000000..dcd8e8ea29455 --- /dev/null +++ b/tests/ui/destructuring-assignment/refutable-enum-assignment.rs @@ -0,0 +1,11 @@ +// Regression test for . + +enum Foo { + One, + Two, +} + +fn main() { + Foo::One = Foo::One; + //~^ ERROR refutable pattern in assignment +} diff --git a/tests/ui/destructuring-assignment/refutable-enum-assignment.stderr b/tests/ui/destructuring-assignment/refutable-enum-assignment.stderr new file mode 100644 index 0000000000000..610cb5ab79280 --- /dev/null +++ b/tests/ui/destructuring-assignment/refutable-enum-assignment.stderr @@ -0,0 +1,21 @@ +error[E0005]: refutable pattern in assignment + --> $DIR/refutable-enum-assignment.rs:9:5 + | +LL | Foo::One = Foo::One; + | ^^^^^^^^ pattern `Foo::Two` not covered + | + = note: destructuring assignments require an "irrefutable pattern", like a `struct` or an `enum` with only one variant + = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html +note: `Foo` defined here + --> $DIR/refutable-enum-assignment.rs:3:6 + | +LL | enum Foo { + | ^^^ +LL | One, +LL | Two, + | --- not covered + = note: the matched value is of type `Foo` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0005`.