about summary refs log tree commit diff
diff options
context:
space:
mode:
authordianne <diannes.gm@gmail.com>2025-03-08 21:07:11 -0800
committerdianne <diannes.gm@gmail.com>2025-04-16 14:42:56 -0700
commite4b7b3d820b6db4f4a37a1a09b700bc9a39d80ba (patch)
tree1010f5219958b81413f96c5d5d8a77110a309cff
parentcb6c499bc98a8b6db85856f200ffba2f47b8b73a (diff)
downloadrust-e4b7b3d820b6db4f4a37a1a09b700bc9a39d80ba.tar.gz
rust-e4b7b3d820b6db4f4a37a1a09b700bc9a39d80ba.zip
pattern typing for immutable implicit deref patterns
-rw-r--r--compiler/rustc_hir_typeck/src/pat.rs116
-rw-r--r--tests/ui/pattern/deref-patterns/bindings.rs27
-rw-r--r--tests/ui/pattern/deref-patterns/branch.rs22
-rw-r--r--tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs18
-rw-r--r--tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr36
-rw-r--r--tests/ui/pattern/deref-patterns/typeck.rs6
-rw-r--r--tests/ui/pattern/deref-patterns/typeck_fail.rs11
-rw-r--r--tests/ui/pattern/deref-patterns/typeck_fail.stderr36
-rw-r--r--tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs21
-rw-r--r--tests/ui/pattern/deref-patterns/unsatisfied-bounds.stderr9
10 files changed, 272 insertions, 30 deletions
diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs
index c19d8cf6dc5..11ed7db9eb7 100644
--- a/compiler/rustc_hir_typeck/src/pat.rs
+++ b/compiler/rustc_hir_typeck/src/pat.rs
@@ -162,12 +162,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 /// Mode for adjusting the expected type and binding mode.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 enum AdjustMode {
-    /// Peel off all immediate reference types.
-    Peel,
+    /// Peel off all immediate reference types. If the `deref_patterns` feature is enabled, this
+    /// also peels smart pointer ADTs.
+    Peel { kind: PeelKind },
     /// Pass on the input binding mode and expected type.
     Pass,
 }
 
+/// Restrictions on what types to peel when adjusting the expected type and binding mode.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum PeelKind {
+    /// Only peel reference types. This is used for explicit `deref!(_)` patterns, which dereference
+    /// any number of `&`/`&mut` references, plus a single smart pointer.
+    ExplicitDerefPat,
+    /// Implicitly peel any number of references, and if `deref_patterns` is enabled, smart pointer
+    /// ADTs. In order to peel only as much as necessary for the pattern to match, the `until_adt`
+    /// field contains the ADT def that the pattern is a constructor for, if applicable, so that we
+    /// don't peel it. See [`ResolvedPat`] for more information.
+    // TODO: add `ResolvedPat` and `until_adt`.
+    Implicit,
+}
+
 /// `ref mut` bindings (explicit or match-ergonomics) are not allowed behind an `&` reference.
 /// Normally, the borrow checker enforces this, but for (currently experimental) match ergonomics,
 /// we track this when typing patterns for two purposes:
@@ -390,7 +405,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
 
         // Resolve type if needed.
-        let expected = if let AdjustMode::Peel = adjust_mode
+        let expected = if let AdjustMode::Peel { .. } = adjust_mode
             && pat.default_binding_modes
         {
             self.try_structurally_resolve_type(pat.span, expected)
@@ -403,7 +418,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         match pat.kind {
             // Peel off a `&` or `&mut` from the scrutinee type. See the examples in
             // `tests/ui/rfcs/rfc-2005-default-binding-mode`.
-            _ if let AdjustMode::Peel = adjust_mode
+            _ if let AdjustMode::Peel { .. } = adjust_mode
                 && pat.default_binding_modes
                 && let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() =>
             {
@@ -443,6 +458,45 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 // Recurse with the new expected type.
                 self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, new_pat_info)
             }
+            // If `deref_patterns` is enabled, peel a smart pointer from the scrutinee type. See the
+            // examples in `tests/ui/pattern/deref_patterns/`.
+            _ if self.tcx.features().deref_patterns()
+                && let AdjustMode::Peel { kind: PeelKind::Implicit } = adjust_mode
+                && pat.default_binding_modes
+                // For simplicity, only apply overloaded derefs if `expected` is a known ADT.
+                // FIXME(deref_patterns): we'll get better diagnostics for users trying to
+                // implicitly deref generics if we allow them here, but primitives, tuples, and
+                // inference vars definitely should be stopped. Figure out what makes most sense.
+                // TODO: stop peeling if the pattern is a constructor for the scrutinee type
+                && expected.is_adt()
+                // At this point, the pattern isn't able to match `expected` without peeling. Check
+                // that it implements `Deref` before assuming it's a smart pointer, to get a normal
+                // type error instead of a missing impl error if not. This only checks for `Deref`,
+                // not `DerefPure`: we require that too, but we want a trait error if it's missing.
+                && let Some(deref_trait) = self.tcx.lang_items().deref_trait()
+                && self
+                    .type_implements_trait(deref_trait, [expected], self.param_env)
+                    .may_apply() =>
+            {
+                debug!("scrutinee ty {expected:?} is a smart pointer, inserting overloaded deref");
+                // The scrutinee is a smart pointer; implicitly dereference it. This adds a
+                // requirement that `expected: DerefPure`.
+                let inner_ty = self.deref_pat_target(pat.span, expected);
+                // Once we've checked `pat`, we'll add a `DerefMut` bound if it contains any
+                // `ref mut` bindings. TODO: implement that, then reference here.
+
+                // Preserve the smart pointer type for THIR lowering and upvar analysis.
+                self.typeck_results
+                    .borrow_mut()
+                    .pat_adjustments_mut()
+                    .entry(pat.hir_id)
+                    .or_default()
+                    .push(PatAdjustment { kind: PatAdjust::OverloadedDeref, source: expected });
+
+                // Recurse, using the old pat info to keep `current_depth` to its old value.
+                // Peeling smart pointers does not update the default binding mode.
+                self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, old_pat_info)
+            }
             PatKind::Missing | PatKind::Wild | PatKind::Err(_) => expected,
             // We allow any type here; we ensure that the type is uninhabited during match checking.
             PatKind::Never => expected,
@@ -505,12 +559,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             PatKind::Struct(..)
             | PatKind::TupleStruct(..)
             | PatKind::Tuple(..)
-            | PatKind::Box(_)
-            | PatKind::Deref(_)
             | PatKind::Range(..)
-            | PatKind::Slice(..) => AdjustMode::Peel,
+            | PatKind::Slice(..) => AdjustMode::Peel { kind: PeelKind::Implicit },
+            // When checking an explicit deref pattern, only peel reference types.
+            // FIXME(deref_patterns): If box patterns and deref patterns need to coexist, box
+            // patterns may want `PeelKind::Implicit`, stopping on encountering a box.
+            | PatKind::Box(_)
+            | PatKind::Deref(_) => AdjustMode::Peel { kind: PeelKind::ExplicitDerefPat },
             // A never pattern behaves somewhat like a literal or unit variant.
-            PatKind::Never => AdjustMode::Peel,
+            PatKind::Never => AdjustMode::Peel { kind: PeelKind::Implicit },
             PatKind::Expr(PatExpr { kind: PatExprKind::Path(_), .. }) => match opt_path_res.unwrap() {
                 // These constants can be of a reference type, e.g. `const X: &u8 = &0;`.
                 // Peeling the reference types too early will cause type checking failures.
@@ -520,7 +577,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 // could successfully compile. The former being `Self` requires a unit struct.
                 // In either case, and unlike constants, the pattern itself cannot be
                 // a reference type wherefore peeling doesn't give up any expressiveness.
-                _ => AdjustMode::Peel,
+                _ => AdjustMode::Peel { kind: PeelKind::Implicit },
             },
 
             // String and byte-string literals result in types `&str` and `&[u8]` respectively.
@@ -530,7 +587,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             // Call `resolve_vars_if_possible` here for inline const blocks.
             PatKind::Expr(lt) => match self.resolve_vars_if_possible(self.check_pat_expr_unadjusted(lt)).kind() {
                 ty::Ref(..) => AdjustMode::Pass,
-                _ => AdjustMode::Peel,
+                _ => AdjustMode::Peel { kind: PeelKind::Implicit },
             },
 
             // Ref patterns are complicated, we handle them in `check_pat_ref`.
@@ -2256,31 +2313,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         expected: Ty<'tcx>,
         pat_info: PatInfo<'tcx>,
     ) -> Ty<'tcx> {
-        let tcx = self.tcx;
-        // Register a `DerefPure` bound, which is required by all `deref!()` pats.
-        self.register_bound(
-            expected,
-            tcx.require_lang_item(hir::LangItem::DerefPure, Some(span)),
-            self.misc(span),
-        );
-        // <expected as Deref>::Target
-        let ty = Ty::new_projection(
-            tcx,
-            tcx.require_lang_item(hir::LangItem::DerefTarget, Some(span)),
-            [expected],
-        );
-        let ty = self.normalize(span, ty);
-        let ty = self.try_structurally_resolve_type(span, ty);
-        self.check_pat(inner, ty, pat_info);
+        let target_ty = self.deref_pat_target(span, expected);
+        self.check_pat(inner, target_ty, pat_info);
 
         // Check if the pattern has any `ref mut` bindings, which would require
         // `DerefMut` to be emitted in MIR building instead of just `Deref`.
         // We do this *after* checking the inner pattern, since we want to make
         // sure to apply any match-ergonomics adjustments.
+        // TODO: move this to a separate definition to share it with implicit deref pats
         if self.typeck_results.borrow().pat_has_ref_mut_binding(inner) {
             self.register_bound(
                 expected,
-                tcx.require_lang_item(hir::LangItem::DerefMut, Some(span)),
+                self.tcx.require_lang_item(hir::LangItem::DerefMut, Some(span)),
                 self.misc(span),
             );
         }
@@ -2288,6 +2332,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         expected
     }
 
+    fn deref_pat_target(&self, span: Span, source_ty: Ty<'tcx>) -> Ty<'tcx> {
+        // Register a `DerefPure` bound, which is required by all `deref!()` pats.
+        let tcx = self.tcx;
+        self.register_bound(
+            source_ty,
+            tcx.require_lang_item(hir::LangItem::DerefPure, Some(span)),
+            self.misc(span),
+        );
+        // The expected type for the deref pat's inner pattern is `<expected as Deref>::Target`.
+        let target_ty = Ty::new_projection(
+            tcx,
+            tcx.require_lang_item(hir::LangItem::DerefTarget, Some(span)),
+            [source_ty],
+        );
+        let target_ty = self.normalize(span, target_ty);
+        self.try_structurally_resolve_type(span, target_ty)
+    }
+
     // Precondition: Pat is Ref(inner)
     fn check_pat_ref(
         &self,
diff --git a/tests/ui/pattern/deref-patterns/bindings.rs b/tests/ui/pattern/deref-patterns/bindings.rs
index 5881e4166a4..ce10cd5439e 100644
--- a/tests/ui/pattern/deref-patterns/bindings.rs
+++ b/tests/ui/pattern/deref-patterns/bindings.rs
@@ -1,7 +1,9 @@
+//@ revisions: explicit implicit
 //@ run-pass
 #![feature(deref_patterns)]
 #![allow(incomplete_features)]
 
+#[cfg(explicit)]
 fn simple_vec(vec: Vec<u32>) -> u32 {
     match vec {
         deref!([]) => 100,
@@ -13,6 +15,19 @@ fn simple_vec(vec: Vec<u32>) -> u32 {
     }
 }
 
+#[cfg(implicit)]
+fn simple_vec(vec: Vec<u32>) -> u32 {
+    match vec {
+        [] => 100,
+        [x] if x == 4 => x + 4,
+        [x] => x,
+        [1, x] => x + 200,
+        deref!(ref slice) => slice.iter().sum(),
+        _ => 2000,
+    }
+}
+
+#[cfg(explicit)]
 fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
     match vecvec {
         deref!([]) => 0,
@@ -24,6 +39,18 @@ fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
     }
 }
 
+#[cfg(implicit)]
+fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
+    match vecvec {
+        [] => 0,
+        [[x]] => x,
+        [[0, x] | [1, x]] => x,
+        [ref x] => x.iter().sum(),
+        [[], [1, x, y]] => y - x,
+        _ => 2000,
+    }
+}
+
 fn ref_mut(val: u32) -> u32 {
     let mut b = Box::new(0u32);
     match &mut b {
diff --git a/tests/ui/pattern/deref-patterns/branch.rs b/tests/ui/pattern/deref-patterns/branch.rs
index 1bac1006d9d..9d72b35fd2f 100644
--- a/tests/ui/pattern/deref-patterns/branch.rs
+++ b/tests/ui/pattern/deref-patterns/branch.rs
@@ -1,8 +1,10 @@
+//@ revisions: explicit implicit
 //@ run-pass
 // Test the execution of deref patterns.
 #![feature(deref_patterns)]
 #![allow(incomplete_features)]
 
+#[cfg(explicit)]
 fn branch(vec: Vec<u32>) -> u32 {
     match vec {
         deref!([]) => 0,
@@ -12,6 +14,17 @@ fn branch(vec: Vec<u32>) -> u32 {
     }
 }
 
+#[cfg(implicit)]
+fn branch(vec: Vec<u32>) -> u32 {
+    match vec {
+        [] => 0,
+        [1, _, 3] => 1,
+        [2, ..] => 2,
+        _ => 1000,
+    }
+}
+
+#[cfg(explicit)]
 fn nested(vec: Vec<Vec<u32>>) -> u32 {
     match vec {
         deref!([deref!([]), ..]) => 1,
@@ -20,6 +33,15 @@ fn nested(vec: Vec<Vec<u32>>) -> u32 {
     }
 }
 
+#[cfg(implicit)]
+fn nested(vec: Vec<Vec<u32>>) -> u32 {
+    match vec {
+        [[], ..] => 1,
+        [[0, ..], [1, ..]] => 2,
+        _ => 1000,
+    }
+}
+
 fn main() {
     assert!(matches!(Vec::<u32>::new(), deref!([])));
     assert!(matches!(vec![1], deref!([1])));
diff --git a/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs b/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs
index 84b5ec09dc7..791776be5ac 100644
--- a/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs
+++ b/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs
@@ -21,4 +21,22 @@ fn cant_move_out_rc(rc: Rc<Struct>) -> Struct {
     }
 }
 
+struct Container(Struct);
+
+fn cant_move_out_box_implicit(b: Box<Container>) -> Struct {
+    match b {
+        //~^ ERROR: cannot move out of a shared reference
+        Container(x) => x,
+        _ => unreachable!(),
+    }
+}
+
+fn cant_move_out_rc_implicit(rc: Rc<Container>) -> Struct {
+    match rc {
+        //~^ ERROR: cannot move out of a shared reference
+        Container(x) => x,
+        _ => unreachable!(),
+    }
+}
+
 fn main() {}
diff --git a/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr b/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr
index 2cf435b1179..1887800fc38 100644
--- a/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr
+++ b/tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr
@@ -32,6 +32,40 @@ help: consider borrowing the pattern binding
 LL |         deref!(ref x) => x,
    |                +++
 
-error: aborting due to 2 previous errors
+error[E0507]: cannot move out of a shared reference
+  --> $DIR/cant_move_out_of_pattern.rs:27:11
+   |
+LL |     match b {
+   |           ^
+LL |
+LL |         Container(x) => x,
+   |                   -
+   |                   |
+   |                   data moved here
+   |                   move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
+   |
+help: consider borrowing the pattern binding
+   |
+LL |         Container(ref x) => x,
+   |                   +++
+
+error[E0507]: cannot move out of a shared reference
+  --> $DIR/cant_move_out_of_pattern.rs:35:11
+   |
+LL |     match rc {
+   |           ^^
+LL |
+LL |         Container(x) => x,
+   |                   -
+   |                   |
+   |                   data moved here
+   |                   move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
+   |
+help: consider borrowing the pattern binding
+   |
+LL |         Container(ref x) => x,
+   |                   +++
+
+error: aborting due to 4 previous errors
 
 For more information about this error, try `rustc --explain E0507`.
diff --git a/tests/ui/pattern/deref-patterns/typeck.rs b/tests/ui/pattern/deref-patterns/typeck.rs
index f23f7042cd8..3a7ce9d1deb 100644
--- a/tests/ui/pattern/deref-patterns/typeck.rs
+++ b/tests/ui/pattern/deref-patterns/typeck.rs
@@ -10,26 +10,32 @@ fn main() {
     let vec: Vec<u32> = Vec::new();
     match vec {
         deref!([..]) => {}
+        [..] => {}
         _ => {}
     }
     match Box::new(true) {
         deref!(true) => {}
+        true => {}
         _ => {}
     }
     match &Box::new(true) {
         deref!(true) => {}
+        true => {}
         _ => {}
     }
     match &Rc::new(0) {
         deref!(1..) => {}
+        1.. => {}
         _ => {}
     }
     let _: &Struct = match &Rc::new(Struct) {
         deref!(x) => x,
+        Struct => &Struct,
         _ => unreachable!(),
     };
     let _: &[Struct] = match &Rc::new(vec![Struct]) {
         deref!(deref!(x)) => x,
+        [Struct] => &[Struct],
         _ => unreachable!(),
     };
 }
diff --git a/tests/ui/pattern/deref-patterns/typeck_fail.rs b/tests/ui/pattern/deref-patterns/typeck_fail.rs
index 040118449ec..4b9ad7d25f0 100644
--- a/tests/ui/pattern/deref-patterns/typeck_fail.rs
+++ b/tests/ui/pattern/deref-patterns/typeck_fail.rs
@@ -7,11 +7,22 @@ fn main() {
     match "foo".to_string() {
         deref!("foo") => {}
         //~^ ERROR: mismatched types
+        "foo" => {}
+        //~^ ERROR: mismatched types
         _ => {}
     }
     match &"foo".to_string() {
         deref!("foo") => {}
         //~^ ERROR: mismatched types
+        "foo" => {}
+        //~^ ERROR: mismatched types
+        _ => {}
+    }
+
+    // Make sure we don't try implicitly dereferncing any ADT.
+    match Some(0) {
+        Ok(0) => {}
+        //~^ ERROR: mismatched types
         _ => {}
     }
 }
diff --git a/tests/ui/pattern/deref-patterns/typeck_fail.stderr b/tests/ui/pattern/deref-patterns/typeck_fail.stderr
index 1c14802745a..3e2f3561882 100644
--- a/tests/ui/pattern/deref-patterns/typeck_fail.stderr
+++ b/tests/ui/pattern/deref-patterns/typeck_fail.stderr
@@ -7,13 +7,45 @@ LL |         deref!("foo") => {}
    |                ^^^^^ expected `str`, found `&str`
 
 error[E0308]: mismatched types
-  --> $DIR/typeck_fail.rs:13:16
+  --> $DIR/typeck_fail.rs:10:9
+   |
+LL |     match "foo".to_string() {
+   |           ----------------- this expression has type `String`
+...
+LL |         "foo" => {}
+   |         ^^^^^ expected `String`, found `&str`
+
+error[E0308]: mismatched types
+  --> $DIR/typeck_fail.rs:15:16
    |
 LL |     match &"foo".to_string() {
    |           ------------------ this expression has type `&String`
 LL |         deref!("foo") => {}
    |                ^^^^^ expected `str`, found `&str`
 
-error: aborting due to 2 previous errors
+error[E0308]: mismatched types
+  --> $DIR/typeck_fail.rs:17:9
+   |
+LL |     match &"foo".to_string() {
+   |           ------------------ this expression has type `&String`
+...
+LL |         "foo" => {}
+   |         ^^^^^ expected `&String`, found `&str`
+   |
+   = note: expected reference `&String`
+              found reference `&'static str`
+
+error[E0308]: mismatched types
+  --> $DIR/typeck_fail.rs:24:9
+   |
+LL |     match Some(0) {
+   |           ------- this expression has type `Option<{integer}>`
+LL |         Ok(0) => {}
+   |         ^^^^^ expected `Option<{integer}>`, found `Result<_, _>`
+   |
+   = note: expected enum `Option<{integer}>`
+              found enum `Result<_, _>`
+
+error: aborting due to 5 previous errors
 
 For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs b/tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs
new file mode 100644
index 00000000000..00064b2320c
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs
@@ -0,0 +1,21 @@
+#![feature(deref_patterns)]
+#![allow(incomplete_features)]
+
+struct MyPointer;
+
+impl std::ops::Deref for MyPointer {
+    type Target = ();
+    fn deref(&self) -> &() {
+        &()
+    }
+}
+
+fn main() {
+    // Test that we get a trait error if a user attempts implicit deref pats on their own impls.
+    // FIXME(deref_patterns): there should be a special diagnostic for missing `DerefPure`.
+    match MyPointer {
+        () => {}
+        //~^ the trait bound `MyPointer: DerefPure` is not satisfied
+        _ => {}
+    }
+}
diff --git a/tests/ui/pattern/deref-patterns/unsatisfied-bounds.stderr b/tests/ui/pattern/deref-patterns/unsatisfied-bounds.stderr
new file mode 100644
index 00000000000..983ce27865c
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/unsatisfied-bounds.stderr
@@ -0,0 +1,9 @@
+error[E0277]: the trait bound `MyPointer: DerefPure` is not satisfied
+  --> $DIR/unsatisfied-bounds.rs:17:9
+   |
+LL |         () => {}
+   |         ^^ the trait `DerefPure` is not implemented for `MyPointer`
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0277`.