about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2024-04-05 16:38:50 +0200
committerGitHub <noreply@github.com>2024-04-05 16:38:50 +0200
commitf2f8d8b722fdacc6a6e02c2289e3e36571749a68 (patch)
tree8c31db33f4f81a9558f8d62ce9af69a111ead861
parentc36c0095776f1cbc39d15a206b4b1018b329b4aa (diff)
parente5376f3947ba8faf0f7c3a9543366060d662357d (diff)
Rollup merge of #123311 - Jules-Bertholet:andpat-everywhere, r=Nadrieril
Match ergonomics: implement "`&`pat everywhere"

Implements the eat-two-layers (feature gate `and_pat_everywhere`, all editions) ~and the eat-one-layer (feature gate `and_eat_one_layer_2024`, edition 2024 only, takes priority on that edition when both feature gates are active)~ (EDIT: will be done in later PR) semantics.

cc #123076

r? ``@Nadrieril``

``@rustbot`` label A-patterns A-edition-2024
-rw-r--r--compiler/rustc_feature/src/unstable.rs2
-rw-r--r--compiler/rustc_hir_typeck/src/pat.rs91
-rw-r--r--compiler/rustc_hir_typeck/src/writeback.rs9
-rw-r--r--compiler/rustc_middle/src/ty/typeck_results.rs56
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/mod.rs9
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--tests/ui/feature-gates/feature-gate-ref_pat_everywhere.rs14
-rw-r--r--tests/ui/feature-gates/feature-gate-ref_pat_everywhere.stderr49
-rw-r--r--tests/ui/match/ref_pat_everywhere-mutability-mismatch.rs16
-rw-r--r--tests/ui/match/ref_pat_everywhere-mutability-mismatch.stderr44
-rw-r--r--tests/ui/match/ref_pat_everywhere.rs18
11 files changed, 281 insertions, 28 deletions
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index 4c975c7b9e0..36db377f7e0 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -567,6 +567,8 @@ declare_features! (
     (unstable, proc_macro_hygiene, "1.30.0", Some(54727)),
     /// Allows `&raw const $place_expr` and `&raw mut $place_expr` expressions.
     (unstable, raw_ref_op, "1.41.0", Some(64490)),
+    /// Allows `&` and `&mut` patterns to consume match-ergonomics-inserted references.
+    (incomplete, ref_pat_everywhere, "CURRENT_RUSTC_VERSION", Some(123076)),
     /// Allows using the `#[register_tool]` attribute.
     (unstable, register_tool, "1.41.0", Some(66079)),
     /// Allows the `#[repr(i128)]` attribute for enums.
diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs
index 9d247c46bab..bb47f8dfba4 100644
--- a/compiler/rustc_hir_typeck/src/pat.rs
+++ b/compiler/rustc_hir_typeck/src/pat.rs
@@ -130,7 +130,14 @@ enum AdjustMode {
     /// Peel off all immediate reference types.
     Peel,
     /// Reset binding mode to the initial mode.
+    /// Used for destructuring assignment, where we don't want any match ergonomics.
     Reset,
+    /// Produced by ref patterns.
+    /// Reset the binding mode to the initial mode,
+    /// and if the old biding mode was by-reference
+    /// with mutability matching the pattern,
+    /// mark the pattern as having consumed this reference.
+    ResetAndConsumeRef(Mutability),
     /// Pass on the input binding mode and expected type.
     Pass,
 }
@@ -174,7 +181,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             _ => None,
         };
         let adjust_mode = self.calc_adjust_mode(pat, path_res.map(|(res, ..)| res));
-        let (expected, def_bm) = self.calc_default_binding_mode(pat, expected, def_bm, adjust_mode);
+        let (expected, def_bm, ref_pattern_already_consumed) =
+            self.calc_default_binding_mode(pat, expected, def_bm, adjust_mode);
         let pat_info = PatInfo {
             binding_mode: def_bm,
             top_info: ti,
@@ -211,7 +219,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             }
             PatKind::Box(inner) => self.check_pat_box(pat.span, inner, expected, pat_info),
             PatKind::Deref(inner) => self.check_pat_deref(pat.span, inner, expected, pat_info),
-            PatKind::Ref(inner, mutbl) => self.check_pat_ref(pat, inner, mutbl, expected, pat_info),
+            PatKind::Ref(inner, mutbl) => self.check_pat_ref(
+                pat,
+                inner,
+                mutbl,
+                expected,
+                pat_info,
+                ref_pattern_already_consumed,
+            ),
             PatKind::Slice(before, slice, after) => {
                 self.check_pat_slice(pat.span, before, slice, after, expected, pat_info)
             }
@@ -264,17 +279,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
     /// Compute the new expected type and default binding mode from the old ones
     /// as well as the pattern form we are currently checking.
+    ///
+    /// Last entry is only relevant for ref patterns (`&` and `&mut`);
+    /// if `true`, then the ref pattern consumed a match ergonomics inserted reference
+    /// and so does no need to match against a reference in the scrutinee type.
     fn calc_default_binding_mode(
         &self,
         pat: &'tcx Pat<'tcx>,
         expected: Ty<'tcx>,
         def_bm: BindingAnnotation,
         adjust_mode: AdjustMode,
-    ) -> (Ty<'tcx>, BindingAnnotation) {
+    ) -> (Ty<'tcx>, BindingAnnotation, bool) {
         match adjust_mode {
-            AdjustMode::Pass => (expected, def_bm),
-            AdjustMode::Reset => (expected, INITIAL_BM),
-            AdjustMode::Peel => self.peel_off_references(pat, expected, def_bm),
+            AdjustMode::Pass => (expected, def_bm, false),
+            AdjustMode::Reset => (expected, INITIAL_BM, false),
+            AdjustMode::ResetAndConsumeRef(mutbl) => {
+                (expected, INITIAL_BM, def_bm.0 == ByRef::Yes(mutbl))
+            }
+            AdjustMode::Peel => {
+                let peeled = self.peel_off_references(pat, expected, def_bm);
+                (peeled.0, peeled.1, false)
+            }
         }
     }
 
@@ -329,7 +354,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             // ```
             //
             // See issue #46688.
-            PatKind::Ref(..) => AdjustMode::Reset,
+            PatKind::Ref(_, mutbl) => AdjustMode::ResetAndConsumeRef(*mutbl),
             // A `_` pattern works with any expected type, so there's no need to do anything.
             PatKind::Wild
             // A malformed pattern doesn't have an expected type, so let's just accept any type.
@@ -840,8 +865,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             && let Some(mt) = self.shallow_resolve(expected).builtin_deref(true)
             && let ty::Dynamic(..) = mt.ty.kind()
         {
-            // This is "x = SomeTrait" being reduced from
-            // "let &x = &SomeTrait" or "let box x = Box<SomeTrait>", an error.
+            // This is "x = dyn SomeTrait" being reduced from
+            // "let &x = &dyn SomeTrait" or "let box x = Box<dyn SomeTrait>", an error.
             let type_str = self.ty_to_string(expected);
             let mut err = struct_span_code_err!(
                 self.dcx(),
@@ -2036,6 +2061,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         mutbl: Mutability,
         expected: Ty<'tcx>,
         pat_info: PatInfo<'tcx, '_>,
+        consumed_inherited_ref: bool,
     ) -> Ty<'tcx> {
         let tcx = self.tcx;
         let expected = self.shallow_resolve(expected);
@@ -2051,26 +2077,37 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 match *expected.kind() {
                     ty::Ref(_, r_ty, r_mutbl) if r_mutbl == mutbl => (expected, r_ty),
                     _ => {
-                        let inner_ty = self.next_ty_var(TypeVariableOrigin {
-                            kind: TypeVariableOriginKind::TypeInference,
-                            span: inner.span,
-                        });
-                        let ref_ty = self.new_ref_ty(pat.span, mutbl, inner_ty);
-                        debug!("check_pat_ref: demanding {:?} = {:?}", expected, ref_ty);
-                        let err = self.demand_eqtype_pat_diag(
-                            pat.span,
-                            expected,
-                            ref_ty,
-                            pat_info.top_info,
-                        );
+                        if consumed_inherited_ref && self.tcx.features().ref_pat_everywhere {
+                            // We already matched against a match-ergonmics inserted reference,
+                            // so we don't need to match against a reference from the original type.
+                            // Save this infor for use in lowering later
+                            self.typeck_results
+                                .borrow_mut()
+                                .skipped_ref_pats_mut()
+                                .insert(pat.hir_id);
+                            (expected, expected)
+                        } else {
+                            let inner_ty = self.next_ty_var(TypeVariableOrigin {
+                                kind: TypeVariableOriginKind::TypeInference,
+                                span: inner.span,
+                            });
+                            let ref_ty = self.new_ref_ty(pat.span, mutbl, inner_ty);
+                            debug!("check_pat_ref: demanding {:?} = {:?}", expected, ref_ty);
+                            let err = self.demand_eqtype_pat_diag(
+                                pat.span,
+                                expected,
+                                ref_ty,
+                                pat_info.top_info,
+                            );
 
-                        // Look for a case like `fn foo(&foo: u32)` and suggest
-                        // `fn foo(foo: &u32)`
-                        if let Some(mut err) = err {
-                            self.borrow_pat_suggestion(&mut err, pat);
-                            err.emit();
+                            // Look for a case like `fn foo(&foo: u32)` and suggest
+                            // `fn foo(foo: &u32)`
+                            if let Some(mut err) = err {
+                                self.borrow_pat_suggestion(&mut err, pat);
+                                err.emit();
+                            }
+                            (ref_ty, inner_ty)
                         }
-                        (ref_ty, inner_ty)
                     }
                 }
             }
diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs
index f4516b684c3..6604bf094c1 100644
--- a/compiler/rustc_hir_typeck/src/writeback.rs
+++ b/compiler/rustc_hir_typeck/src/writeback.rs
@@ -345,6 +345,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> {
             _ => {}
         };
 
+        self.visit_skipped_ref_pats(p.hir_id);
         self.visit_pat_adjustments(p.span, p.hir_id);
 
         self.visit_node_id(p.span, p.hir_id);
@@ -674,6 +675,14 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
         }
     }
 
+    #[instrument(skip(self), level = "debug")]
+    fn visit_skipped_ref_pats(&mut self, hir_id: hir::HirId) {
+        if self.fcx.typeck_results.borrow_mut().skipped_ref_pats_mut().remove(hir_id) {
+            debug!("node is a skipped ref pat");
+            self.typeck_results.skipped_ref_pats_mut().insert(hir_id);
+        }
+    }
+
     fn visit_liberated_fn_sigs(&mut self) {
         let fcx_typeck_results = self.fcx.typeck_results.borrow();
         assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs
index 0d74524276f..995feeaa148 100644
--- a/compiler/rustc_middle/src/ty/typeck_results.rs
+++ b/compiler/rustc_middle/src/ty/typeck_results.rs
@@ -96,6 +96,10 @@ pub struct TypeckResults<'tcx> {
     /// <https://github.com/rust-lang/rfcs/blob/master/text/2005-match-ergonomics.md#definitions>
     pat_adjustments: ItemLocalMap<Vec<Ty<'tcx>>>,
 
+    /// Set of reference patterns that match against a match-ergonomics inserted reference
+    /// (as opposed to against a reference in the scrutinee type).
+    skipped_ref_pats: ItemLocalSet,
+
     /// Records the reasons that we picked the kind of each closure;
     /// not all closures are present in the map.
     closure_kind_origins: ItemLocalMap<(Span, HirPlace<'tcx>)>,
@@ -228,6 +232,7 @@ impl<'tcx> TypeckResults<'tcx> {
             adjustments: Default::default(),
             pat_binding_modes: Default::default(),
             pat_adjustments: Default::default(),
+            skipped_ref_pats: Default::default(),
             closure_kind_origins: Default::default(),
             liberated_fn_sigs: Default::default(),
             fru_field_types: Default::default(),
@@ -435,6 +440,14 @@ impl<'tcx> TypeckResults<'tcx> {
         LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.pat_adjustments }
     }
 
+    pub fn skipped_ref_pats(&self) -> LocalSetInContext<'_> {
+        LocalSetInContext { hir_owner: self.hir_owner, data: &self.skipped_ref_pats }
+    }
+
+    pub fn skipped_ref_pats_mut(&mut self) -> LocalSetInContextMut<'_> {
+        LocalSetInContextMut { hir_owner: self.hir_owner, data: &mut self.skipped_ref_pats }
+    }
+
     /// Does the pattern recursively contain a `ref mut` binding in it?
     ///
     /// This is used to determined whether a `deref` pattern should emit a `Deref`
@@ -629,6 +642,49 @@ impl<'a, V> LocalTableInContextMut<'a, V> {
     }
 }
 
+#[derive(Clone, Copy, Debug)]
+pub struct LocalSetInContext<'a> {
+    hir_owner: OwnerId,
+    data: &'a ItemLocalSet,
+}
+
+impl<'a> LocalSetInContext<'a> {
+    pub fn is_empty(&self) -> bool {
+        self.data.is_empty()
+    }
+
+    pub fn contains(&self, id: hir::HirId) -> bool {
+        validate_hir_id_for_typeck_results(self.hir_owner, id);
+        self.data.contains(&id.local_id)
+    }
+}
+
+#[derive(Debug)]
+pub struct LocalSetInContextMut<'a> {
+    hir_owner: OwnerId,
+    data: &'a mut ItemLocalSet,
+}
+
+impl<'a> LocalSetInContextMut<'a> {
+    pub fn is_empty(&self) -> bool {
+        self.data.is_empty()
+    }
+
+    pub fn contains(&self, id: hir::HirId) -> bool {
+        validate_hir_id_for_typeck_results(self.hir_owner, id);
+        self.data.contains(&id.local_id)
+    }
+    pub fn insert(&mut self, id: hir::HirId) -> bool {
+        validate_hir_id_for_typeck_results(self.hir_owner, id);
+        self.data.insert(id.local_id)
+    }
+
+    pub fn remove(&mut self, id: hir::HirId) -> bool {
+        validate_hir_id_for_typeck_results(self.hir_owner, id);
+        self.data.remove(&id.local_id)
+    }
+}
+
 rustc_index::newtype_index! {
     #[derive(HashStable)]
     #[encodable]
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index a4992da679e..133cf8e3349 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -65,7 +65,14 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
         // we wrap the unadjusted pattern in `PatKind::Deref` repeatedly, consuming the
         // adjustments in *reverse order* (last-in-first-out, so that the last `Deref` inserted
         // gets the least-dereferenced type).
-        let unadjusted_pat = self.lower_pattern_unadjusted(pat);
+        let unadjusted_pat = match pat.kind {
+            hir::PatKind::Ref(inner, _)
+                if self.typeck_results.skipped_ref_pats().contains(pat.hir_id) =>
+            {
+                self.lower_pattern_unadjusted(inner)
+            }
+            _ => self.lower_pattern_unadjusted(pat),
+        };
         self.typeck_results.pat_adjustments().get(pat.hir_id).unwrap_or(&vec![]).iter().rev().fold(
             unadjusted_pat,
             |pat: Box<_>, ref_ty| {
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index a13c9c9b278..ea0f7adf6f9 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1456,6 +1456,7 @@ symbols! {
         receiver,
         recursion_limit,
         reexport_test_harness_main,
+        ref_pat_everywhere,
         ref_unwind_safe_trait,
         reference,
         reflect,
diff --git a/tests/ui/feature-gates/feature-gate-ref_pat_everywhere.rs b/tests/ui/feature-gates/feature-gate-ref_pat_everywhere.rs
new file mode 100644
index 00000000000..ed5db56e0e8
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-ref_pat_everywhere.rs
@@ -0,0 +1,14 @@
+pub fn main() {
+    if let Some(Some(&x)) = &Some(&Some(0)) {
+        //~^ ERROR: mismatched types [E0308]
+        let _: u32 = x;
+    }
+    if let Some(&Some(x)) = &Some(Some(0)) {
+        //~^ ERROR: mismatched types [E0308]
+        let _: u32 = x;
+    }
+    if let Some(Some(&mut x)) = &mut Some(&mut Some(0)) {
+        //~^ ERROR: mismatched types [E0308]
+        let _: u32 = x;
+    }
+}
diff --git a/tests/ui/feature-gates/feature-gate-ref_pat_everywhere.stderr b/tests/ui/feature-gates/feature-gate-ref_pat_everywhere.stderr
new file mode 100644
index 00000000000..0f0051325cd
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-ref_pat_everywhere.stderr
@@ -0,0 +1,49 @@
+error[E0308]: mismatched types
+  --> $DIR/feature-gate-ref_pat_everywhere.rs:2:22
+   |
+LL |     if let Some(Some(&x)) = &Some(&Some(0)) {
+   |                      ^^     --------------- this expression has type `&Option<&Option<{integer}>>`
+   |                      |
+   |                      expected integer, found `&_`
+   |
+   = note:   expected type `{integer}`
+           found reference `&_`
+help: consider removing `&` from the pattern
+   |
+LL |     if let Some(Some(x)) = &Some(&Some(0)) {
+   |                      ~
+
+error[E0308]: mismatched types
+  --> $DIR/feature-gate-ref_pat_everywhere.rs:6:17
+   |
+LL |     if let Some(&Some(x)) = &Some(Some(0)) {
+   |                 ^^^^^^^^    -------------- this expression has type `&Option<Option<{integer}>>`
+   |                 |
+   |                 expected `Option<{integer}>`, found `&_`
+   |
+   = note:   expected enum `Option<{integer}>`
+           found reference `&_`
+
+error[E0308]: mismatched types
+  --> $DIR/feature-gate-ref_pat_everywhere.rs:10:22
+   |
+LL |     if let Some(Some(&mut x)) = &mut Some(&mut Some(0)) {
+   |                      ^^^^^^     ----------------------- this expression has type `&mut Option<&mut Option<{integer}>>`
+   |                      |
+   |                      expected integer, found `&mut _`
+   |
+   = note:           expected type `{integer}`
+           found mutable reference `&mut _`
+note: to declare a mutable binding use: `mut x`
+  --> $DIR/feature-gate-ref_pat_everywhere.rs:10:22
+   |
+LL |     if let Some(Some(&mut x)) = &mut Some(&mut Some(0)) {
+   |                      ^^^^^^
+help: consider removing `&mut` from the pattern
+   |
+LL |     if let Some(Some(x)) = &mut Some(&mut Some(0)) {
+   |                      ~
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/match/ref_pat_everywhere-mutability-mismatch.rs b/tests/ui/match/ref_pat_everywhere-mutability-mismatch.rs
new file mode 100644
index 00000000000..9dd7a7893ec
--- /dev/null
+++ b/tests/ui/match/ref_pat_everywhere-mutability-mismatch.rs
@@ -0,0 +1,16 @@
+#![allow(incomplete_features)]
+#![feature(ref_pat_everywhere)]
+pub fn main() {
+    if let Some(&x) = Some(0) {
+        //~^ ERROR: mismatched types [E0308]
+        let _: u32 = x;
+    }
+    if let &Some(x) = &mut Some(0) {
+        //~^ ERROR: mismatched types [E0308]
+        let _: u32 = x;
+    }
+    if let Some(&x) = &mut Some(0) {
+        //~^ ERROR: mismatched types [E0308]
+        let _: u32 = x;
+    }
+}
diff --git a/tests/ui/match/ref_pat_everywhere-mutability-mismatch.stderr b/tests/ui/match/ref_pat_everywhere-mutability-mismatch.stderr
new file mode 100644
index 00000000000..d512ea5f957
--- /dev/null
+++ b/tests/ui/match/ref_pat_everywhere-mutability-mismatch.stderr
@@ -0,0 +1,44 @@
+error[E0308]: mismatched types
+  --> $DIR/ref_pat_everywhere-mutability-mismatch.rs:4:17
+   |
+LL |     if let Some(&x) = Some(0) {
+   |                 ^^    ------- this expression has type `Option<{integer}>`
+   |                 |
+   |                 expected integer, found `&_`
+   |
+   = note:   expected type `{integer}`
+           found reference `&_`
+help: consider removing `&` from the pattern
+   |
+LL |     if let Some(x) = Some(0) {
+   |                 ~
+
+error[E0308]: mismatched types
+  --> $DIR/ref_pat_everywhere-mutability-mismatch.rs:8:12
+   |
+LL |     if let &Some(x) = &mut Some(0) {
+   |            ^^^^^^^^   ------------ this expression has type `&mut Option<{integer}>`
+   |            |
+   |            types differ in mutability
+   |
+   = note: expected mutable reference `&mut Option<{integer}>`
+                      found reference `&_`
+
+error[E0308]: mismatched types
+  --> $DIR/ref_pat_everywhere-mutability-mismatch.rs:12:17
+   |
+LL |     if let Some(&x) = &mut Some(0) {
+   |                 ^^    ------------ this expression has type `&mut Option<{integer}>`
+   |                 |
+   |                 expected integer, found `&_`
+   |
+   = note:   expected type `{integer}`
+           found reference `&_`
+help: consider removing `&` from the pattern
+   |
+LL |     if let Some(x) = &mut Some(0) {
+   |                 ~
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/match/ref_pat_everywhere.rs b/tests/ui/match/ref_pat_everywhere.rs
new file mode 100644
index 00000000000..b3daca48409
--- /dev/null
+++ b/tests/ui/match/ref_pat_everywhere.rs
@@ -0,0 +1,18 @@
+//@ run-pass
+#![allow(incomplete_features)]
+#![feature(ref_pat_everywhere)]
+
+pub fn main() {
+    if let Some(Some(&x)) = &Some(&Some(0)) {
+        let _: u32 = x;
+    }
+    if let Some(&Some(x)) = &Some(Some(0)) {
+        let _: u32 = x;
+    }
+    if let Some(Some(&mut x)) = &mut Some(&mut Some(0)) {
+        let _: u32 = x;
+    }
+    if let Some(Some(&x)) = &Some(&mut Some(0)) {
+        let _: u32 = x;
+    }
+}