Skip to content

Commit

Permalink
Lower deref patterns to MIR
Browse files Browse the repository at this point in the history
This handles using deref patterns to choose the correct match arm. This
does not handle bindings or guards.

Co-authored-by: Deadbeef <[email protected]>
  • Loading branch information
Nadrieril and fee1-dead committed Apr 20, 2024
1 parent a61b14d commit c623319
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 39 deletions.
8 changes: 7 additions & 1 deletion compiler/rustc_hir_typeck/src/mem_categorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,13 +711,19 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> {
self.cat_pattern_(place_with_id, subpat, op)?;
}

PatKind::Box(subpat) | PatKind::Ref(subpat, _) | PatKind::Deref(subpat) => {
PatKind::Box(subpat) | PatKind::Ref(subpat, _) => {
// box p1, &p1, &mut p1. we can ignore the mutability of
// PatKind::Ref since that information is already contained
// in the type.
let subplace = self.cat_deref(pat, place_with_id)?;
self.cat_pattern_(subplace, subpat, op)?;
}
PatKind::Deref(subpat) => {
let ty = self.pat_ty_adjusted(subpat)?;
// A deref pattern generates a temporary.
let place = self.cat_rvalue(pat.hir_id, ty);
self.cat_pattern_(place, subpat, op)?;
}

PatKind::Slice(before, ref slice, after) => {
let Some(element_ty) = place_with_id.place.ty().builtin_index() else {
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_mir_build/src/build/matches/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,7 @@ enum TestCase<'pat, 'tcx> {
Constant { value: mir::Const<'tcx> },
Range(&'pat PatRange<'tcx>),
Slice { len: usize, variable_length: bool },
Deref { temp: Place<'tcx> },
Or { pats: Box<[FlatPat<'pat, 'tcx>]> },
}

Expand Down Expand Up @@ -1222,6 +1223,12 @@ enum TestKind<'tcx> {

/// Test that the length of the slice is equal to `len`.
Len { len: u64, op: BinOp },

/// Call `Deref::deref` on the value.
Deref {
/// Temporary to store the result of `deref()`.
temp: Place<'tcx>,
},
}

/// A test to perform to determine which [`Candidate`] matches a value.
Expand Down
88 changes: 61 additions & 27 deletions compiler/rustc_mir_build/src/build/matches/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
TestKind::Len { len: len as u64, op }
}

TestCase::Deref { temp } => TestKind::Deref { temp },

TestCase::Or { .. } => bug!("or-patterns should have already been handled"),

TestCase::Irrefutable { .. } => span_bug!(
Expand Down Expand Up @@ -143,35 +145,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
);
}
let re_erased = tcx.lifetimes.re_erased;
let ref_string = self.temp(Ty::new_imm_ref(tcx, re_erased, ty), test.span);
let ref_str_ty = Ty::new_imm_ref(tcx, re_erased, tcx.types.str_);
let ref_str = self.temp(ref_str_ty, test.span);
let deref = tcx.require_lang_item(LangItem::Deref, None);
let method = trait_method(tcx, deref, sym::deref, [ty]);
let eq_block = self.cfg.start_new_block();
self.cfg.push_assign(
block,
source_info,
ref_string,
Rvalue::Ref(re_erased, BorrowKind::Shared, place),
);
self.cfg.terminate(
block,
source_info,
TerminatorKind::Call {
func: Operand::Constant(Box::new(ConstOperand {
span: test.span,
user_ty: None,
const_: method,
})),
args: vec![Spanned { node: Operand::Move(ref_string), span: DUMMY_SP }],
destination: ref_str,
target: Some(eq_block),
unwind: UnwindAction::Continue,
call_source: CallSource::Misc,
fn_span: source_info.span,
},
);
// `let ref_str: &str = <String as Deref>::deref(&place);`
self.call_deref(block, eq_block, place, ty, ref_str, test.span);
self.non_scalar_compare(
eq_block,
success_block,
Expand Down Expand Up @@ -270,9 +248,57 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
Operand::Move(expected),
);
}

TestKind::Deref { temp } => {
let ty = place_ty.ty;
let target = target_block(TestBranch::Success);
self.call_deref(block, target, place, ty, temp, test.span);
}
}
}

/// Perform `let temp = <ty as Deref>::deref(&place)`.
pub(super) fn call_deref(
&mut self,
block: BasicBlock,
target_block: BasicBlock,
place: Place<'tcx>,
ty: Ty<'tcx>,
temp: Place<'tcx>,
span: Span,
) {
let source_info = self.source_info(span);
let re_erased = self.tcx.lifetimes.re_erased;
let deref = self.tcx.require_lang_item(LangItem::Deref, None);
let method = trait_method(self.tcx, deref, sym::deref, [ty]);
let ref_src = self.temp(Ty::new_imm_ref(self.tcx, re_erased, ty), span);
// `let ref_src = &src_place;`
self.cfg.push_assign(
block,
source_info,
ref_src,
Rvalue::Ref(re_erased, BorrowKind::Shared, place),
);
// `let temp = <Ty as Deref>::deref(ref_src);`
self.cfg.terminate(
block,
source_info,
TerminatorKind::Call {
func: Operand::Constant(Box::new(ConstOperand {
span,
user_ty: None,
const_: method,
})),
args: vec![Spanned { node: Operand::Move(ref_src), span }],
destination: temp,
target: Some(target_block),
unwind: UnwindAction::Continue,
call_source: CallSource::Misc,
fn_span: source_info.span,
},
);
}

/// Compare using the provided built-in comparison operator
fn compare(
&mut self,
Expand Down Expand Up @@ -660,13 +686,21 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
}

(TestKind::Deref { temp: test_temp }, TestCase::Deref { temp })
if test_temp == temp =>
{
fully_matched = true;
Some(TestBranch::Success)
}

(
TestKind::Switch { .. }
| TestKind::SwitchInt { .. }
| TestKind::If
| TestKind::Len { .. }
| TestKind::Range { .. }
| TestKind::Eq { .. },
| TestKind::Eq { .. }
| TestKind::Deref { .. },
_,
) => {
fully_matched = false;
Expand Down
15 changes: 10 additions & 5 deletions compiler/rustc_mir_build/src/build/matches/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use rustc_data_structures::fx::FxIndexSet;
use rustc_infer::infer::type_variable::TypeVariableOrigin;
use rustc_middle::mir::*;
use rustc_middle::thir::{self, *};
use rustc_middle::ty;
use rustc_middle::ty::TypeVisitableExt;
use rustc_middle::ty::{self, Ty};

impl<'a, 'tcx> Builder<'a, 'tcx> {
pub(crate) fn field_match_pairs<'pat>(
Expand Down Expand Up @@ -249,10 +249,15 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
default_irrefutable()
}

PatKind::DerefPattern { .. } => {
// FIXME(deref_patterns)
// Treat it like a wildcard for now.
default_irrefutable()
PatKind::DerefPattern { ref subpattern } => {
// Create a new temporary for each deref pattern.
// FIXME(deref_patterns): dedup temporaries to avoid multiple `deref()` calls?
let temp = cx.temp(
Ty::new_imm_ref(cx.tcx, cx.tcx.lifetimes.re_erased, subpattern.ty),
pattern.span,
);
subpairs.push(MatchPair::new(PlaceBuilder::from(temp).deref(), subpattern, cx));
TestCase::Deref { temp }
}
};

Expand Down
37 changes: 37 additions & 0 deletions tests/ui/pattern/deref-patterns/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//@ run-pass
#![feature(deref_patterns)]
#![allow(incomplete_features)]

fn simple_vec(vec: Vec<u32>) -> u32 {
match vec {
deref!([]) => 100,
// FIXME(deref_patterns): fake borrows break guards
// deref!([x]) if x == 4 => x + 4,
deref!([x]) => x,
deref!([1, x]) => x + 200,
deref!(ref slice) => slice.iter().sum(),
_ => 2000,
}
}

fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
match vecvec {
deref!([]) => 0,
deref!([deref!([x])]) => x,
deref!([deref!([0, x]) | deref!([1, x])]) => x,
deref!([ref x]) => x.iter().sum(),
deref!([deref!([]), deref!([1, x, y])]) => y - x,
_ => 2000,
}
}

fn main() {
assert_eq!(simple_vec(vec![1]), 1);
assert_eq!(simple_vec(vec![1, 2]), 202);
assert_eq!(simple_vec(vec![1, 2, 3]), 6);

assert_eq!(nested_vec(vec![vec![0, 42]]), 42);
assert_eq!(nested_vec(vec![vec![1, 42]]), 42);
assert_eq!(nested_vec(vec![vec![1, 2, 3]]), 6);
assert_eq!(nested_vec(vec![vec![], vec![1, 2, 3]]), 1);
}
40 changes: 40 additions & 0 deletions tests/ui/pattern/deref-patterns/branch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//@ run-pass
// Test the execution of deref patterns.
#![feature(deref_patterns)]
#![allow(incomplete_features)]

fn branch(vec: Vec<u32>) -> u32 {
match vec {
deref!([]) => 0,
deref!([1, _, 3]) => 1,
deref!([2, ..]) => 2,
_ => 1000,
}
}

fn nested(vec: Vec<Vec<u32>>) -> u32 {
match vec {
deref!([deref!([]), ..]) => 1,
deref!([deref!([0, ..]), deref!([1, ..])]) => 2,
_ => 1000,
}
}

fn main() {
assert!(matches!(Vec::<u32>::new(), deref!([])));
assert!(matches!(vec![1], deref!([1])));
assert!(matches!(&vec![1], deref!([1])));
assert!(matches!(vec![&1], deref!([1])));
assert!(matches!(vec![vec![1]], deref!([deref!([1])])));

assert_eq!(branch(vec![]), 0);
assert_eq!(branch(vec![1, 2, 3]), 1);
assert_eq!(branch(vec![3, 2, 1]), 1000);
assert_eq!(branch(vec![2]), 2);
assert_eq!(branch(vec![2, 3]), 2);
assert_eq!(branch(vec![3, 2]), 1000);

assert_eq!(nested(vec![vec![], vec![2]]), 1);
assert_eq!(nested(vec![vec![0], vec![1]]), 2);
assert_eq!(nested(vec![vec![0, 2], vec![1, 2]]), 2);
}
24 changes: 24 additions & 0 deletions tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#![feature(deref_patterns)]
#![allow(incomplete_features)]

use std::rc::Rc;

struct Struct;

fn cant_move_out_box(b: Box<Struct>) -> Struct {
match b {
//~^ ERROR: cannot move out of a shared reference
deref!(x) => x,
_ => unreachable!(),
}
}

fn cant_move_out_rc(rc: Rc<Struct>) -> Struct {
match rc {
//~^ ERROR: cannot move out of a shared reference
deref!(x) => x,
_ => unreachable!(),
}
}

fn main() {}
27 changes: 27 additions & 0 deletions tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
error[E0507]: cannot move out of a shared reference
--> $DIR/cant_move_out_of_pattern.rs:9:11
|
LL | match b {
| ^
LL |
LL | deref!(x) => x,
| -
| |
| data moved here
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait

error[E0507]: cannot move out of a shared reference
--> $DIR/cant_move_out_of_pattern.rs:17:11
|
LL | match rc {
| ^^
LL |
LL | deref!(x) => x,
| -
| |
| data moved here
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0507`.
16 changes: 10 additions & 6 deletions tests/ui/pattern/deref-patterns/typeck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use std::rc::Rc;

struct Struct;

fn main() {
let vec: Vec<u32> = Vec::new();
match vec {
Expand All @@ -22,10 +24,12 @@ fn main() {
deref!(1..) => {}
_ => {}
}
// FIXME(deref_patterns): fails to typecheck because `"foo"` has type &str but deref creates a
// place of type `str`.
// match "foo".to_string() {
// box "foo" => {}
// _ => {}
// }
let _: &Struct = match &Rc::new(Struct) {
deref!(x) => x,
_ => unreachable!(),
};
let _: &[Struct] = match &Rc::new(vec![Struct]) {
deref!(deref!(x)) => x,
_ => unreachable!(),
};
}
17 changes: 17 additions & 0 deletions tests/ui/pattern/deref-patterns/typeck_fail.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![feature(deref_patterns)]
#![allow(incomplete_features)]

fn main() {
// FIXME(deref_patterns): fails to typecheck because `"foo"` has type &str but deref creates a
// place of type `str`.
match "foo".to_string() {
deref!("foo") => {}
//~^ ERROR: mismatched types
_ => {}
}
match &"foo".to_string() {
deref!("foo") => {}
//~^ ERROR: mismatched types
_ => {}
}
}
19 changes: 19 additions & 0 deletions tests/ui/pattern/deref-patterns/typeck_fail.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
error[E0308]: mismatched types
--> $DIR/typeck_fail.rs:8:16
|
LL | match "foo".to_string() {
| ----------------- this expression has type `String`
LL | deref!("foo") => {}
| ^^^^^ expected `str`, found `&str`

error[E0308]: mismatched types
--> $DIR/typeck_fail.rs:13: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

For more information about this error, try `rustc --explain E0308`.

0 comments on commit c623319

Please sign in to comment.