Skip to content

Commit

Permalink
Implement "&<pat> everywhere"
Browse files Browse the repository at this point in the history
The original proposal allows reference patterns
with "compatible" mutability, however it's not clear
what that means so for now we require an exact match.

I don't know the type system code well, so if something
seems to not make sense it's probably because I made a
mistake
  • Loading branch information
Jules-Bertholet committed Mar 30, 2024
1 parent 877d36b commit f37a4d5
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 29 deletions.
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ declare_features! (
(incomplete, adt_const_params, "1.56.0", Some(95174)),
/// Allows defining an `#[alloc_error_handler]`.
(unstable, alloc_error_handler, "1.29.0", Some(51540)),
/// Allows `&` and `&mut` patterns to consume match-ergonomics-inserted references.
(incomplete, and_pat_everywhere, "CURRENT_RUSTC_VERSION", Some(123076)),
/// Allows trait methods with arbitrary self types.
(unstable, arbitrary_self_types, "1.23.0", Some(44874)),
/// Allows using `const` operands in inline assembly.
Expand Down
88 changes: 61 additions & 27 deletions compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ enum AdjustMode {
Peel,
/// Reset binding mode to the initial mode.
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.
RefReset(Mutability),
/// Pass on the input binding mode and expected type.
Pass,
}
Expand Down Expand Up @@ -174,7 +180,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 +218,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 +278,25 @@ 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::RefReset(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 +351,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// ```
//
// See issue #46688.
PatKind::Ref(..) => AdjustMode::Reset,
PatKind::Ref(_, mutbl) => AdjustMode::RefReset(*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 +862,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 +2058,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
mutbl: Mutability,
expected: Ty<'tcx>,
pat_info: PatInfo<'tcx, '_>,
already_consumed: bool,
) -> Ty<'tcx> {
let tcx = self.tcx;
let expected = self.shallow_resolve(expected);
Expand All @@ -2051,26 +2074,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 already_consumed && self.tcx.features().and_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()
.ref_pats_that_dont_deref_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
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> {
_ => {}
};

self.visit_ref_pats_that_dont_deref(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_ref_pats_that_dont_deref(&mut self, hir_id: hir::HirId) {
if self.fcx.typeck_results.borrow_mut().ref_pats_that_dont_deref_mut().remove(hir_id) {
debug!("node is a ref pat that doesn't deref");
self.typeck_results.ref_pats_that_dont_deref_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
Original file line number Diff line number Diff line change
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).
ref_pats_that_dont_deref: 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(),
ref_pats_that_dont_deref: 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 ref_pats_that_dont_deref(&self) -> LocalSetInContext<'_> {
LocalSetInContext { hir_owner: self.hir_owner, data: &self.ref_pats_that_dont_deref }
}

pub fn ref_pats_that_dont_deref_mut(&mut self) -> LocalSetInContextMut<'_> {
LocalSetInContextMut { hir_owner: self.hir_owner, data: &mut self.ref_pats_that_dont_deref }
}

/// 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
11 changes: 9 additions & 2 deletions compiler/rustc_mir_build/src/thir/pattern/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,16 @@ 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 = if self.typeck_results.ref_pats_that_dont_deref().contains(pat.hir_id) {
match pat.kind {
hir::PatKind::Ref(inner, _) => self.lower_pattern_unadjusted(inner),
_ => span_bug!(pat.span, "non ref pattern marked as non-deref ref pattern"),
}
} else {
self.lower_pattern_unadjusted(pat)
};
self.typeck_results.pat_adjustments().get(pat.hir_id).unwrap_or(&vec![]).iter().rev().fold(
unadjusted_pat,
unadjusted,
|pat: Box<_>, ref_ty| {
debug!("{:?}: wrapping pattern with type {:?}", pat, ref_ty);
Box::new(Pat {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ symbols! {
alu32,
always,
and,
and_pat_everywhere,
and_then,
anon,
anon_adt,
Expand Down
12 changes: 12 additions & 0 deletions tests/ui/match/and_pat_everywhere-mutability-mismatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#![allow(incomplete_features)]
#![feature(and_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;
}
}
29 changes: 29 additions & 0 deletions tests/ui/match/and_pat_everywhere-mutability-mismatch.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
error[E0308]: mismatched types
--> $DIR/and_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/and_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: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.
15 changes: 15 additions & 0 deletions tests/ui/match/and_pat_everywhere.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//@ run-pass
#![allow(incomplete_features)]
#![feature(and_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;
}
}
14 changes: 14 additions & 0 deletions tests/ui/match/feature-gate-and_pat_everywhere.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit f37a4d5

Please sign in to comment.