about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-05-08 02:16:45 +0000
committerbors <bors@rust-lang.org>2025-05-08 02:16:45 +0000
commit7e552b46af72df390ed233b58a7f51650515b2a8 (patch)
treef8aec230f54c32fdc51d778a9856fc753a6ceb1a
parentae3e8c6191fb2bf9394ea4201adaf7b1ac496120 (diff)
parent09fed2d2f440fff2179ca9373bb16e40fc81d935 (diff)
downloadrust-7e552b46af72df390ed233b58a7f51650515b2a8.tar.gz
rust-7e552b46af72df390ed233b58a7f51650515b2a8.zip
Auto merge of #140106 - dianne:deref-pat-usefulness, r=Nadrieril
allow deref patterns to participate in exhaustiveness analysis

Per [this proposal](https://hackmd.io/4qDDMcvyQ-GDB089IPcHGg#Exhaustiveness), this PR allows deref patterns to participate in exhaustiveness analysis. Currently all deref patterns enforce `DerefPure` bounds on their scrutinees, so this assumes all patterns it's analyzing are well-behaved. This also doesn't support [mixed exhaustiveness](https://hackmd.io/4qDDMcvyQ-GDB089IPcHGg#Mixed-exhaustiveness), and instead emits an error if deref patterns are used together with normal constructors. I think mixed exhaustiveness would be nice to have (especially if we eventually want to support arbitrary `Deref` impls[^1]), but it'd require more work to get reasonable diagnostics[^2].

Tracking issue for deref patterns: #87121

r? `@Nadrieril`

[^1]: Regardless of whether we support limited exhaustiveness checking for untrusted `Deref` or always require other arms to be exhaustive, I think it'd be useful to allow mixed matching for user-defined smart pointers. And it'd be strange if it worked there but not for `Cow`.

[^2]: I think listing out witnesses of non-exhaustiveness can be confusing when they're not necessarily disjoint, and when you only need to cover some of them, so we'd probably want special formatting and/or explanatory subdiagnostics. And if it's implemented similarly to unions, we'd probably also want some way of merging witnesses; the way witnesses for unions can appear duplicated is pretty unfortunate. I'm not sure yet how the diagnostics should look, especially for deeply nested patterns.
-rw-r--r--compiler/rustc_pattern_analysis/messages.ftl4
-rw-r--r--compiler/rustc_pattern_analysis/src/constructor.rs22
-rw-r--r--compiler/rustc_pattern_analysis/src/errors.rs14
-rw-r--r--compiler/rustc_pattern_analysis/src/rustc.rs71
-rw-r--r--compiler/rustc_pattern_analysis/src/usefulness.rs4
-rw-r--r--src/doc/unstable-book/src/language-features/deref-patterns.md3
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs3
-rw-r--r--tests/ui/pattern/deref-patterns/bindings.rs2
-rw-r--r--tests/ui/pattern/deref-patterns/closure_capture.rs8
-rw-r--r--tests/ui/pattern/deref-patterns/deref-box.rs10
-rw-r--r--tests/ui/pattern/deref-patterns/implicit-cow-deref.rs4
-rw-r--r--tests/ui/pattern/deref-patterns/usefulness/empty-types.rs47
-rw-r--r--tests/ui/pattern/deref-patterns/usefulness/empty-types.stderr38
-rw-r--r--tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.rs48
-rw-r--r--tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.stderr43
-rw-r--r--tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.rs28
-rw-r--r--tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.stderr63
-rw-r--r--tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.rs33
-rw-r--r--tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.stderr60
19 files changed, 480 insertions, 25 deletions
diff --git a/compiler/rustc_pattern_analysis/messages.ftl b/compiler/rustc_pattern_analysis/messages.ftl
index 41a1d958f10..d3a3107f8e8 100644
--- a/compiler/rustc_pattern_analysis/messages.ftl
+++ b/compiler/rustc_pattern_analysis/messages.ftl
@@ -6,6 +6,10 @@ pattern_analysis_excluside_range_missing_max = exclusive range missing `{$max}`
     .label = this range doesn't match `{$max}` because `..` is an exclusive range
     .suggestion = use an inclusive range instead
 
+pattern_analysis_mixed_deref_pattern_constructors = mix of deref patterns and normal constructors
+    .deref_pattern_label = matches on the result of dereferencing `{$smart_pointer_ty}`
+    .normal_constructor_label = matches directly on `{$smart_pointer_ty}`
+
 pattern_analysis_non_exhaustive_omitted_pattern = some variants are not matched explicitly
     .help = ensure that all variants are matched explicitly by adding the suggested match arms
     .note = the matched value is of type `{$scrut_ty}` and the `non_exhaustive_omitted_patterns` attribute was found
diff --git a/compiler/rustc_pattern_analysis/src/constructor.rs b/compiler/rustc_pattern_analysis/src/constructor.rs
index 4ce868f014f..f7a4931c111 100644
--- a/compiler/rustc_pattern_analysis/src/constructor.rs
+++ b/compiler/rustc_pattern_analysis/src/constructor.rs
@@ -696,6 +696,10 @@ pub enum Constructor<Cx: PatCx> {
     F128Range(IeeeFloat<QuadS>, IeeeFloat<QuadS>, RangeEnd),
     /// String literals. Strings are not quite the same as `&[u8]` so we treat them separately.
     Str(Cx::StrLit),
+    /// Deref patterns (enabled by the `deref_patterns` feature) provide a way of matching on a
+    /// smart pointer ADT through its pointee. They don't directly correspond to ADT constructors,
+    /// and currently are not supported alongside them. Carries the type of the pointee.
+    DerefPattern(Cx::Ty),
     /// Constants that must not be matched structurally. They are treated as black boxes for the
     /// purposes of exhaustiveness: we must not inspect them, and they don't count towards making a
     /// match exhaustive.
@@ -740,6 +744,7 @@ impl<Cx: PatCx> Clone for Constructor<Cx> {
             Constructor::F64Range(lo, hi, end) => Constructor::F64Range(*lo, *hi, *end),
             Constructor::F128Range(lo, hi, end) => Constructor::F128Range(*lo, *hi, *end),
             Constructor::Str(value) => Constructor::Str(value.clone()),
+            Constructor::DerefPattern(ty) => Constructor::DerefPattern(ty.clone()),
             Constructor::Opaque(inner) => Constructor::Opaque(inner.clone()),
             Constructor::Or => Constructor::Or,
             Constructor::Never => Constructor::Never,
@@ -856,6 +861,10 @@ impl<Cx: PatCx> Constructor<Cx> {
             }
             (Slice(self_slice), Slice(other_slice)) => self_slice.is_covered_by(*other_slice),
 
+            // Deref patterns only interact with other deref patterns. Prior to usefulness analysis,
+            // we ensure they don't appear alongside any other non-wild non-opaque constructors.
+            (DerefPattern(_), DerefPattern(_)) => true,
+
             // Opaque constructors don't interact with anything unless they come from the
             // syntactically identical pattern.
             (Opaque(self_id), Opaque(other_id)) => self_id == other_id,
@@ -932,6 +941,7 @@ impl<Cx: PatCx> Constructor<Cx> {
             F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}")?,
             F128Range(lo, hi, end) => write!(f, "{lo}{end}{hi}")?,
             Str(value) => write!(f, "{value:?}")?,
+            DerefPattern(_) => write!(f, "deref!({:?})", fields.next().unwrap())?,
             Opaque(..) => write!(f, "<constant pattern>")?,
             Or => {
                 for pat in fields {
@@ -1039,8 +1049,17 @@ impl<Cx: PatCx> ConstructorSet<Cx> {
         let mut missing = Vec::new();
         // Constructors in `ctors`, except wildcards and opaques.
         let mut seen = Vec::new();
+        // If we see a deref pattern, it must be the only non-wildcard non-opaque constructor; we
+        // ensure this prior to analysis.
+        let mut deref_pat_present = false;
         for ctor in ctors.cloned() {
             match ctor {
+                DerefPattern(..) => {
+                    if !deref_pat_present {
+                        deref_pat_present = true;
+                        present.push(ctor);
+                    }
+                }
                 Opaque(..) => present.push(ctor),
                 Wildcard => {} // discard wildcards
                 _ => seen.push(ctor),
@@ -1048,6 +1067,9 @@ impl<Cx: PatCx> ConstructorSet<Cx> {
         }
 
         match self {
+            _ if deref_pat_present => {
+                // Deref patterns are the only constructor; nothing is missing.
+            }
             ConstructorSet::Struct { empty } => {
                 if !seen.is_empty() {
                     present.push(Struct);
diff --git a/compiler/rustc_pattern_analysis/src/errors.rs b/compiler/rustc_pattern_analysis/src/errors.rs
index e60930d6cd2..156ba973767 100644
--- a/compiler/rustc_pattern_analysis/src/errors.rs
+++ b/compiler/rustc_pattern_analysis/src/errors.rs
@@ -1,5 +1,5 @@
 use rustc_errors::{Diag, EmissionGuarantee, Subdiagnostic};
-use rustc_macros::{LintDiagnostic, Subdiagnostic};
+use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
 use rustc_middle::ty::Ty;
 use rustc_span::Span;
 
@@ -133,3 +133,15 @@ pub(crate) struct NonExhaustiveOmittedPatternLintOnArm {
     pub lint_level: &'static str,
     pub lint_name: &'static str,
 }
+
+#[derive(Diagnostic)]
+#[diag(pattern_analysis_mixed_deref_pattern_constructors)]
+pub(crate) struct MixedDerefPatternConstructors<'tcx> {
+    #[primary_span]
+    pub spans: Vec<Span>,
+    pub smart_pointer_ty: Ty<'tcx>,
+    #[label(pattern_analysis_deref_pattern_label)]
+    pub deref_pattern_label: Span,
+    #[label(pattern_analysis_normal_constructor_label)]
+    pub normal_constructor_label: Span,
+}
diff --git a/compiler/rustc_pattern_analysis/src/rustc.rs b/compiler/rustc_pattern_analysis/src/rustc.rs
index 7c12f69f14c..a89d01dcbbe 100644
--- a/compiler/rustc_pattern_analysis/src/rustc.rs
+++ b/compiler/rustc_pattern_analysis/src/rustc.rs
@@ -269,6 +269,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
                 }
                 _ => bug!("bad slice pattern {:?} {:?}", ctor, ty),
             },
+            DerefPattern(pointee_ty) => reveal_and_alloc(cx, once(pointee_ty.inner())),
             Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
             | F128Range(..) | Str(..) | Opaque(..) | Never | NonExhaustive | Hidden | Missing
             | PrivateUninhabited | Wildcard => &[],
@@ -296,7 +297,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
                 }
                 _ => bug!("Unexpected type for constructor `{ctor:?}`: {ty:?}"),
             },
-            Ref => 1,
+            Ref | DerefPattern(_) => 1,
             Slice(slice) => slice.arity(),
             Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
             | F128Range(..) | Str(..) | Opaque(..) | Never | NonExhaustive | Hidden | Missing
@@ -493,11 +494,15 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
                     ),
                 };
             }
-            PatKind::DerefPattern { .. } => {
-                // FIXME(deref_patterns): At least detect that `box _` is irrefutable.
-                fields = vec![];
-                arity = 0;
-                ctor = Opaque(OpaqueId::new());
+            PatKind::DerefPattern { subpattern, .. } => {
+                // NB(deref_patterns): This assumes the deref pattern is matching on a trusted
+                // `DerefPure` type. If the `Deref` impl isn't trusted, exhaustiveness must take
+                // into account that multiple calls to deref may return different results. Hence
+                // multiple deref! patterns cannot be exhaustive together unless each is exhaustive
+                // by itself.
+                fields = vec![self.lower_pat(subpattern).at_index(0)];
+                arity = 1;
+                ctor = DerefPattern(cx.reveal_opaque_ty(subpattern.ty));
             }
             PatKind::Leaf { subpatterns } | PatKind::Variant { subpatterns, .. } => {
                 match ty.kind() {
@@ -874,6 +879,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
                 print::write_ref_like(&mut s, pat.ty().inner(), &print(&pat.fields[0])).unwrap();
                 s
             }
+            DerefPattern(_) => format!("deref!({})", print(&pat.fields[0])),
             Slice(slice) => {
                 let (prefix_len, has_dot_dot) = match slice.kind {
                     SliceKind::FixedLen(len) => (len, false),
@@ -1100,6 +1106,14 @@ pub fn analyze_match<'p, 'tcx>(
     scrut_ty: Ty<'tcx>,
 ) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
     let scrut_ty = tycx.reveal_opaque_ty(scrut_ty);
+
+    // The analysis doesn't support deref patterns mixed with normal constructors; error if present.
+    // FIXME(deref_patterns): This only needs to run when a deref pattern was found during lowering.
+    if tycx.tcx.features().deref_patterns() {
+        let pat_column = PatternColumn::new(arms);
+        detect_mixed_deref_pat_ctors(tycx, &pat_column)?;
+    }
+
     let scrut_validity = PlaceValidity::from_bool(tycx.known_valid_scrutinee);
     let report = compute_match_usefulness(
         tycx,
@@ -1119,6 +1133,51 @@ pub fn analyze_match<'p, 'tcx>(
     Ok(report)
 }
 
+// FIXME(deref_patterns): Currently it's the responsibility of the frontend (rustc or rust-analyzer)
+// to ensure that deref patterns don't appear in the same column as normal constructors. Deref
+// patterns aren't currently implemented in rust-analyzer, but should they be, the columnwise check
+// here could be made generic and shared between frontends.
+fn detect_mixed_deref_pat_ctors<'p, 'tcx>(
+    cx: &RustcPatCtxt<'p, 'tcx>,
+    column: &PatternColumn<'p, RustcPatCtxt<'p, 'tcx>>,
+) -> Result<(), ErrorGuaranteed> {
+    let Some(&ty) = column.head_ty() else {
+        return Ok(());
+    };
+
+    // Check for a mix of deref patterns and normal constructors.
+    let mut normal_ctor_span = None;
+    let mut deref_pat_span = None;
+    for pat in column.iter() {
+        match pat.ctor() {
+            // The analysis can handle mixing deref patterns with wildcards and opaque patterns.
+            Wildcard | Opaque(_) => {}
+            DerefPattern(_) => deref_pat_span = Some(pat.data().span),
+            // Nothing else can be compared to deref patterns in `Constructor::is_covered_by`.
+            _ => normal_ctor_span = Some(pat.data().span),
+        }
+    }
+    if let Some(normal_constructor_label) = normal_ctor_span
+        && let Some(deref_pattern_label) = deref_pat_span
+    {
+        return Err(cx.tcx.dcx().emit_err(errors::MixedDerefPatternConstructors {
+            spans: vec![deref_pattern_label, normal_constructor_label],
+            smart_pointer_ty: ty.inner(),
+            deref_pattern_label,
+            normal_constructor_label,
+        }));
+    }
+
+    // Specialize and recurse into the patterns' fields.
+    let set = column.analyze_ctors(cx, &ty)?;
+    for ctor in set.present {
+        for specialized_column in column.specialize(cx, &ty, &ctor).iter() {
+            detect_mixed_deref_pat_ctors(cx, specialized_column)?;
+        }
+    }
+    Ok(())
+}
+
 struct RecursiveOpaque {
     def_id: DefId,
 }
diff --git a/compiler/rustc_pattern_analysis/src/usefulness.rs b/compiler/rustc_pattern_analysis/src/usefulness.rs
index 11ebbea07fa..53638f2a57d 100644
--- a/compiler/rustc_pattern_analysis/src/usefulness.rs
+++ b/compiler/rustc_pattern_analysis/src/usefulness.rs
@@ -702,6 +702,7 @@
 //!   - `ui/consts/const_in_pattern`
 //!   - `ui/rfc-2008-non-exhaustive`
 //!   - `ui/half-open-range-patterns`
+//!   - `ui/pattern/deref-patterns`
 //!   - probably many others
 //!
 //! I (Nadrieril) prefer to put new tests in `ui/pattern/usefulness` unless there's a specific
@@ -866,7 +867,8 @@ impl PlaceValidity {
     /// inside `&` and union fields where validity is reset to `MaybeInvalid`.
     fn specialize<Cx: PatCx>(self, ctor: &Constructor<Cx>) -> Self {
         // We preserve validity except when we go inside a reference or a union field.
-        if matches!(ctor, Constructor::Ref | Constructor::UnionField) {
+        if matches!(ctor, Constructor::Ref | Constructor::DerefPattern(_) | Constructor::UnionField)
+        {
             // Validity of `x: &T` does not imply validity of `*x: T`.
             MaybeInvalid
         } else {
diff --git a/src/doc/unstable-book/src/language-features/deref-patterns.md b/src/doc/unstable-book/src/language-features/deref-patterns.md
index fb6df290cc1..4c3d456b9af 100644
--- a/src/doc/unstable-book/src/language-features/deref-patterns.md
+++ b/src/doc/unstable-book/src/language-features/deref-patterns.md
@@ -60,8 +60,7 @@ Like [`box_patterns`], deref patterns may move out of boxes:
 # #![feature(deref_patterns)]
 # #![allow(incomplete_features)]
 struct NoCopy;
-// Match exhaustiveness analysis is not yet implemented.
-let deref!(x) = Box::new(NoCopy) else { unreachable!() };
+let deref!(x) = Box::new(NoCopy);
 drop::<NoCopy>(x);
 ```
 
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
index 6323d8b71b7..068fc22f2ca 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
@@ -301,6 +301,7 @@ impl<'db> MatchCheckCtx<'db> {
             // ignore this issue.
             Ref => PatKind::Deref { subpattern: subpatterns.next().unwrap() },
             Slice(_) => unimplemented!(),
+            DerefPattern(_) => unimplemented!(),
             &Str(void) => match void {},
             Wildcard | NonExhaustive | Hidden | PrivateUninhabited => PatKind::Wild,
             Never => PatKind::Never,
@@ -351,6 +352,7 @@ impl PatCx for MatchCheckCtx<'_> {
             },
             Ref => 1,
             Slice(..) => unimplemented!(),
+            DerefPattern(..) => unimplemented!(),
             Never | Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
             | F128Range(..) | Str(..) | Opaque(..) | NonExhaustive | PrivateUninhabited
             | Hidden | Missing | Wildcard => 0,
@@ -411,6 +413,7 @@ impl PatCx for MatchCheckCtx<'_> {
                 }
             },
             Slice(_) => unreachable!("Found a `Slice` constructor in match checking"),
+            DerefPattern(_) => unreachable!("Found a `DerefPattern` constructor in match checking"),
             Never | Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
             | F128Range(..) | Str(..) | Opaque(..) | NonExhaustive | PrivateUninhabited
             | Hidden | Missing | Wildcard => {
diff --git a/tests/ui/pattern/deref-patterns/bindings.rs b/tests/ui/pattern/deref-patterns/bindings.rs
index ac48e3ffefc..92c01d737ba 100644
--- a/tests/ui/pattern/deref-patterns/bindings.rs
+++ b/tests/ui/pattern/deref-patterns/bindings.rs
@@ -13,7 +13,6 @@ fn simple_vec(vec: Vec<u32>) -> u32 {
         deref!([x]) => x,
         deref!([1, x]) => x + 200,
         deref!(ref slice) => slice.iter().sum(),
-        _ => 2000,
     }
 }
 
@@ -25,7 +24,6 @@ fn simple_vec(vec: Vec<u32>) -> u32 {
         [x] => x,
         [1, x] => x + 200,
         deref!(ref slice) => slice.iter().sum(),
-        _ => 2000,
     }
 }
 
diff --git a/tests/ui/pattern/deref-patterns/closure_capture.rs b/tests/ui/pattern/deref-patterns/closure_capture.rs
index cf78eeda1d5..497ec622b0c 100644
--- a/tests/ui/pattern/deref-patterns/closure_capture.rs
+++ b/tests/ui/pattern/deref-patterns/closure_capture.rs
@@ -9,7 +9,7 @@ struct NoCopy;
 fn main() {
     let b = Rc::new("aaa".to_string());
     let f = || {
-        let deref!(ref s) = b else { unreachable!() };
+        let deref!(ref s) = b;
         assert_eq!(s.len(), 3);
     };
     assert_eq!(b.len(), 3);
@@ -26,7 +26,7 @@ fn main() {
 
     let mut b = "aaa".to_string();
     let mut f = || {
-        let deref!(ref mut s) = b else { unreachable!() };
+        let deref!(ref mut s) = b;
         s.make_ascii_uppercase();
     };
     f();
@@ -53,7 +53,7 @@ fn main() {
     let b = Box::new(NoCopy);
     let f = || {
         // this should move out of the box rather than borrow.
-        let deref!(x) = b else { unreachable!() };
+        let deref!(x) = b;
         drop::<NoCopy>(x);
     };
     f();
@@ -61,7 +61,7 @@ fn main() {
     let b = Box::new((NoCopy,));
     let f = || {
         // this should move out of the box rather than borrow.
-        let (x,) = b else { unreachable!() };
+        let (x,) = b;
         drop::<NoCopy>(x);
     };
     f();
diff --git a/tests/ui/pattern/deref-patterns/deref-box.rs b/tests/ui/pattern/deref-patterns/deref-box.rs
index 2d0a8d01972..39b23dcab51 100644
--- a/tests/ui/pattern/deref-patterns/deref-box.rs
+++ b/tests/ui/pattern/deref-patterns/deref-box.rs
@@ -6,18 +6,18 @@
 #![expect(incomplete_features)]
 
 fn unbox_1<T>(b: Box<T>) -> T {
-    let deref!(x) = b else { unreachable!() };
+    let deref!(x) = b;
     x
 }
 
 fn unbox_2<T>(b: Box<(T,)>) -> T {
-    let (x,) = b else { unreachable!() };
+    let (x,) = b;
     x
 }
 
 fn unbox_separately<T>(b: Box<(T, T)>) -> (T, T) {
-    let (x, _) = b else { unreachable!() };
-    let (_, y) = b else { unreachable!() };
+    let (x, _) = b;
+    let (_, y) = b;
     (x, y)
 }
 
@@ -31,7 +31,7 @@ fn main() {
 
     // test that borrowing from a box also works
     let mut b = "hi".to_owned().into_boxed_str();
-    let deref!(ref mut s) = b else { unreachable!() };
+    let deref!(ref mut s) = b;
     s.make_ascii_uppercase();
     assert_eq!(&*b, "HI");
 }
diff --git a/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs b/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs
index 04c83d4c33f..24770261edc 100644
--- a/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs
+++ b/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs
@@ -11,7 +11,6 @@ fn main() {
 
     match cow {
         [..] => {}
-        _ => unreachable!(),
     }
 
     match cow {
@@ -22,14 +21,12 @@ fn main() {
     match Rc::new(&cow) {
         Cow::Borrowed { 0: _ } => {}
         Cow::Owned { 0: _ } => unreachable!(),
-        _ => unreachable!(),
     }
 
     let cow_of_cow: Cow<'_, Cow<'static, [u8]>> = Cow::Owned(cow);
 
     match cow_of_cow {
         [..] => {}
-        _ => unreachable!(),
     }
 
     // This matches on the outer `Cow` (the owned one).
@@ -41,6 +38,5 @@ fn main() {
     match Rc::new(&cow_of_cow) {
         Cow::Borrowed { 0: _ } => unreachable!(),
         Cow::Owned { 0: _ } => {}
-        _ => unreachable!(),
     }
 }
diff --git a/tests/ui/pattern/deref-patterns/usefulness/empty-types.rs b/tests/ui/pattern/deref-patterns/usefulness/empty-types.rs
new file mode 100644
index 00000000000..03419030e72
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/usefulness/empty-types.rs
@@ -0,0 +1,47 @@
+//! Test that the place behind a deref pattern is treated as maybe-invalid, and thus empty arms
+//! cannot be omitted. This is handled the same as for refs and union fields, so this leaves the
+//! bulk of the testing to `tests/ui/pattern/usefulness/empty-types.rs`.
+// FIXME(deref_patterns): On stabilization, cases for deref patterns could be worked into that file
+// to keep the tests for empty types in one place and test more thoroughly.
+#![feature(deref_patterns)]
+#![expect(incomplete_features)]
+#![deny(unreachable_patterns)]
+
+enum Void {}
+
+fn main() {
+    // Sanity check: matching on an empty type without pointer indirection lets us omit arms.
+    let opt_void: Option<Void> = None;
+    match opt_void {
+        None => {}
+    }
+
+    // But if we hide it behind a smart pointer, we need an arm.
+    let box_opt_void: Box<Option<Void>> = Box::new(None);
+    match box_opt_void {
+        //~^ ERROR non-exhaustive patterns: `deref!(Some(_))` not covered
+        None => {}
+    }
+    match box_opt_void {
+        None => {}
+        Some(_) => {}
+    }
+    match box_opt_void {
+        None => {}
+        _ => {}
+    }
+
+    // For consistency, this behaves the same as if we manually dereferenced the scrutinee.
+    match *box_opt_void {
+        //~^ ERROR non-exhaustive patterns: `Some(_)` not covered
+        None => {}
+    }
+    match *box_opt_void {
+        None => {}
+        Some(_) => {}
+    }
+    match *box_opt_void {
+        None => {}
+        _ => {}
+    }
+}
diff --git a/tests/ui/pattern/deref-patterns/usefulness/empty-types.stderr b/tests/ui/pattern/deref-patterns/usefulness/empty-types.stderr
new file mode 100644
index 00000000000..e3247708566
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/usefulness/empty-types.stderr
@@ -0,0 +1,38 @@
+error[E0004]: non-exhaustive patterns: `deref!(Some(_))` not covered
+  --> $DIR/empty-types.rs:21:11
+   |
+LL |     match box_opt_void {
+   |           ^^^^^^^^^^^^ pattern `deref!(Some(_))` not covered
+   |
+note: `Box<Option<Void>>` defined here
+  --> $SRC_DIR/alloc/src/boxed.rs:LL:COL
+   = note: the matched value is of type `Box<Option<Void>>`
+   = note: `Void` is uninhabited but is not being matched by value, so a wildcard `_` is required
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
+   |
+LL ~         None => {},
+LL +         deref!(Some(_)) => todo!()
+   |
+
+error[E0004]: non-exhaustive patterns: `Some(_)` not covered
+  --> $DIR/empty-types.rs:35:11
+   |
+LL |     match *box_opt_void {
+   |           ^^^^^^^^^^^^^ pattern `Some(_)` not covered
+   |
+note: `Option<Void>` defined here
+  --> $SRC_DIR/core/src/option.rs:LL:COL
+  ::: $SRC_DIR/core/src/option.rs:LL:COL
+   |
+   = note: not covered
+   = note: the matched value is of type `Option<Void>`
+   = note: `Void` is uninhabited but is not being matched by value, so a wildcard `_` is required
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
+   |
+LL ~         None => {},
+LL +         Some(_) => todo!()
+   |
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0004`.
diff --git a/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.rs b/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.rs
new file mode 100644
index 00000000000..f567dc07bb5
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.rs
@@ -0,0 +1,48 @@
+//! Test matches with a mix of ADT constructors and deref patterns. Currently, usefulness analysis
+//! doesn't support this, so make sure we catch it beforehand. As a consequence, it takes priority
+//! over non-exhaustive match and unreachable pattern errors.
+#![feature(deref_patterns)]
+#![expect(incomplete_features)]
+#![deny(unreachable_patterns)]
+
+use std::borrow::Cow;
+
+fn main() {
+    let cow: Cow<'static, bool> = Cow::Borrowed(&false);
+
+    match cow {
+        true => {}
+        //~v ERROR mix of deref patterns and normal constructors
+        false => {}
+        Cow::Borrowed(_) => {}
+    }
+
+    match cow {
+        Cow::Owned(_) => {}
+        Cow::Borrowed(_) => {}
+        true => {}
+        //~^ ERROR mix of deref patterns and normal constructors
+    }
+
+    match cow {
+        _ => {}
+        Cow::Owned(_) => {}
+        false => {}
+        //~^ ERROR mix of deref patterns and normal constructors
+    }
+
+    match (cow, 0) {
+        (Cow::Owned(_), 0) => {}
+        (Cow::Borrowed(_), 0) => {}
+        (true, 0) => {}
+        //~^ ERROR mix of deref patterns and normal constructors
+    }
+
+    match (0, cow) {
+        (0, Cow::Owned(_)) => {}
+        (0, Cow::Borrowed(_)) => {}
+        _ => {}
+        (1, true) => {}
+        //~^ ERROR mix of deref patterns and normal constructors
+    }
+}
diff --git a/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.stderr b/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.stderr
new file mode 100644
index 00000000000..5ad24164b98
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/usefulness/mixed-constructors.stderr
@@ -0,0 +1,43 @@
+error: mix of deref patterns and normal constructors
+  --> $DIR/mixed-constructors.rs:16:9
+   |
+LL |         false => {}
+   |         ^^^^^ matches on the result of dereferencing `Cow<'_, bool>`
+LL |         Cow::Borrowed(_) => {}
+   |         ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
+
+error: mix of deref patterns and normal constructors
+  --> $DIR/mixed-constructors.rs:22:9
+   |
+LL |         Cow::Borrowed(_) => {}
+   |         ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
+LL |         true => {}
+   |         ^^^^ matches on the result of dereferencing `Cow<'_, bool>`
+
+error: mix of deref patterns and normal constructors
+  --> $DIR/mixed-constructors.rs:29:9
+   |
+LL |         Cow::Owned(_) => {}
+   |         ^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
+LL |         false => {}
+   |         ^^^^^ matches on the result of dereferencing `Cow<'_, bool>`
+
+error: mix of deref patterns and normal constructors
+  --> $DIR/mixed-constructors.rs:36:10
+   |
+LL |         (Cow::Borrowed(_), 0) => {}
+   |          ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
+LL |         (true, 0) => {}
+   |          ^^^^ matches on the result of dereferencing `Cow<'_, bool>`
+
+error: mix of deref patterns and normal constructors
+  --> $DIR/mixed-constructors.rs:43:13
+   |
+LL |         (0, Cow::Borrowed(_)) => {}
+   |             ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
+LL |         _ => {}
+LL |         (1, true) => {}
+   |             ^^^^ matches on the result of dereferencing `Cow<'_, bool>`
+
+error: aborting due to 5 previous errors
+
diff --git a/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.rs b/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.rs
new file mode 100644
index 00000000000..704cae8bdbc
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.rs
@@ -0,0 +1,28 @@
+//! Test non-exhaustive matches involving deref patterns.
+#![feature(deref_patterns)]
+#![expect(incomplete_features)]
+#![deny(unreachable_patterns)]
+
+fn main() {
+    match Box::new(false) {
+        //~^ ERROR non-exhaustive patterns: `deref!(true)` not covered
+        false => {}
+    }
+
+    match Box::new(Box::new(false)) {
+        //~^ ERROR non-exhaustive patterns: `deref!(deref!(false))` not covered
+        true => {}
+    }
+
+    match Box::new((true, Box::new(false))) {
+        //~^ ERROR non-exhaustive patterns: `deref!((false, deref!(false)))` and `deref!((true, deref!(true)))` not covered
+        (true, false) => {}
+        (false, true) => {}
+    }
+
+    enum T { A, B, C }
+    match Box::new((Box::new(T::A), Box::new(T::A))) {
+        //~^ ERROR non-exhaustive patterns: `deref!((deref!(T::C), _))` not covered
+        (T::A | T::B, T::C) => {}
+    }
+}
diff --git a/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.stderr b/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.stderr
new file mode 100644
index 00000000000..55fa84bafde
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/usefulness/non-exhaustive.stderr
@@ -0,0 +1,63 @@
+error[E0004]: non-exhaustive patterns: `deref!(true)` not covered
+  --> $DIR/non-exhaustive.rs:7:11
+   |
+LL |     match Box::new(false) {
+   |           ^^^^^^^^^^^^^^^ pattern `deref!(true)` not covered
+   |
+note: `Box<bool>` defined here
+  --> $SRC_DIR/alloc/src/boxed.rs:LL:COL
+   = note: the matched value is of type `Box<bool>`
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
+   |
+LL ~         false => {},
+LL +         deref!(true) => todo!()
+   |
+
+error[E0004]: non-exhaustive patterns: `deref!(deref!(false))` not covered
+  --> $DIR/non-exhaustive.rs:12:11
+   |
+LL |     match Box::new(Box::new(false)) {
+   |           ^^^^^^^^^^^^^^^^^^^^^^^^^ pattern `deref!(deref!(false))` not covered
+   |
+note: `Box<Box<bool>>` defined here
+  --> $SRC_DIR/alloc/src/boxed.rs:LL:COL
+   = note: the matched value is of type `Box<Box<bool>>`
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
+   |
+LL ~         true => {},
+LL +         deref!(deref!(false)) => todo!()
+   |
+
+error[E0004]: non-exhaustive patterns: `deref!((false, deref!(false)))` and `deref!((true, deref!(true)))` not covered
+  --> $DIR/non-exhaustive.rs:17:11
+   |
+LL |     match Box::new((true, Box::new(false))) {
+   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ patterns `deref!((false, deref!(false)))` and `deref!((true, deref!(true)))` not covered
+   |
+note: `Box<(bool, Box<bool>)>` defined here
+  --> $SRC_DIR/alloc/src/boxed.rs:LL:COL
+   = note: the matched value is of type `Box<(bool, Box<bool>)>`
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
+   |
+LL ~         (false, true) => {},
+LL +         deref!((false, deref!(false))) | deref!((true, deref!(true))) => todo!()
+   |
+
+error[E0004]: non-exhaustive patterns: `deref!((deref!(T::C), _))` not covered
+  --> $DIR/non-exhaustive.rs:24:11
+   |
+LL |     match Box::new((Box::new(T::A), Box::new(T::A))) {
+   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pattern `deref!((deref!(T::C), _))` not covered
+   |
+note: `Box<(Box<T>, Box<T>)>` defined here
+  --> $SRC_DIR/alloc/src/boxed.rs:LL:COL
+   = note: the matched value is of type `Box<(Box<T>, Box<T>)>`
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
+   |
+LL ~         (T::A | T::B, T::C) => {},
+LL +         deref!((deref!(T::C), _)) => todo!()
+   |
+
+error: aborting due to 4 previous errors
+
+For more information about this error, try `rustc --explain E0004`.
diff --git a/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.rs b/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.rs
new file mode 100644
index 00000000000..2677fc54ded
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.rs
@@ -0,0 +1,33 @@
+//! Test unreachable patterns involving deref patterns.
+#![feature(deref_patterns)]
+#![expect(incomplete_features)]
+#![deny(unreachable_patterns)]
+
+fn main() {
+    match Box::new(false) {
+        true => {}
+        false => {}
+        false => {} //~ ERROR unreachable pattern
+    }
+
+    match Box::new(Box::new(false)) {
+        true => {}
+        false => {}
+        true => {} //~ ERROR unreachable pattern
+    }
+
+    match Box::new((true, Box::new(false))) {
+        (true, _) => {}
+        (_, true) => {}
+        (false, false) => {}
+        _ => {} //~ ERROR unreachable pattern
+    }
+
+    enum T { A, B, C }
+    match Box::new((Box::new(T::A), Box::new(T::A))) {
+        (T::A | T::B, T::A | T::C) => {}
+        (T::A, T::C) => {} //~ ERROR unreachable pattern
+        (T::B, T::A) => {} //~ ERROR unreachable pattern
+        _ => {}
+    }
+}
diff --git a/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.stderr b/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.stderr
new file mode 100644
index 00000000000..045e11be319
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/usefulness/unreachable-patterns.stderr
@@ -0,0 +1,60 @@
+error: unreachable pattern
+  --> $DIR/unreachable-patterns.rs:10:9
+   |
+LL |         false => {}
+   |         ----- matches all the relevant values
+LL |         false => {}
+   |         ^^^^^ no value can reach this
+   |
+note: the lint level is defined here
+  --> $DIR/unreachable-patterns.rs:4:9
+   |
+LL | #![deny(unreachable_patterns)]
+   |         ^^^^^^^^^^^^^^^^^^^^
+
+error: unreachable pattern
+  --> $DIR/unreachable-patterns.rs:16:9
+   |
+LL |         true => {}
+   |         ---- matches all the relevant values
+LL |         false => {}
+LL |         true => {}
+   |         ^^^^ no value can reach this
+
+error: unreachable pattern
+  --> $DIR/unreachable-patterns.rs:23:9
+   |
+LL |         _ => {}
+   |         ^ no value can reach this
+   |
+note: multiple earlier patterns match some of the same values
+  --> $DIR/unreachable-patterns.rs:23:9
+   |
+LL |         (true, _) => {}
+   |         --------- matches some of the same values
+LL |         (_, true) => {}
+   |         --------- matches some of the same values
+LL |         (false, false) => {}
+   |         -------------- matches some of the same values
+LL |         _ => {}
+   |         ^ collectively making this unreachable
+
+error: unreachable pattern
+  --> $DIR/unreachable-patterns.rs:29:9
+   |
+LL |         (T::A | T::B, T::A | T::C) => {}
+   |         -------------------------- matches all the relevant values
+LL |         (T::A, T::C) => {}
+   |         ^^^^^^^^^^^^ no value can reach this
+
+error: unreachable pattern
+  --> $DIR/unreachable-patterns.rs:30:9
+   |
+LL |         (T::A | T::B, T::A | T::C) => {}
+   |         -------------------------- matches all the relevant values
+LL |         (T::A, T::C) => {}
+LL |         (T::B, T::A) => {}
+   |         ^^^^^^^^^^^^ no value can reach this
+
+error: aborting due to 5 previous errors
+