Skip to content

Commit

Permalink
Rollup merge of rust-lang#123311 - Jules-Bertholet:andpat-everywhere,…
Browse files Browse the repository at this point in the history
… 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 rust-lang#123076

r? `@Nadrieril`

`@rustbot` label A-patterns A-edition-2024
  • Loading branch information
matthiaskrgr committed Apr 4, 2024
2 parents a4b11c8 + e5376f3 commit 086c8a0
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 28 deletions.
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Expand Up @@ -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.
Expand Down
91 changes: 64 additions & 27 deletions compiler/rustc_hir_typeck/src/pat.rs
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
}
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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);
Expand All @@ -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)
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_hir_typeck/src/writeback.rs
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
56 changes: 56 additions & 0 deletions compiler/rustc_middle/src/ty/typeck_results.rs
Expand Up @@ -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>)>,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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]
Expand Down
9 changes: 8 additions & 1 deletion compiler/rustc_mir_build/src/thir/pattern/mod.rs
Expand Up @@ -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| {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Expand Up @@ -1456,6 +1456,7 @@ symbols! {
receiver,
recursion_limit,
reexport_test_harness_main,
ref_pat_everywhere,
ref_unwind_safe_trait,
reference,
reflect,
Expand Down
14 changes: 14 additions & 0 deletions 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;
}
}
49 changes: 49 additions & 0 deletions 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`.
16 changes: 16 additions & 0 deletions 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;
}
}

0 comments on commit 086c8a0

Please sign in to comment.