Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Match ergonomics: implement "&pat everywhere" #123311

Merged
merged 3 commits into from Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
}
}