Lower deref patterns to MIR

This handles using deref patterns to choose the correct match arm. This
does not handle bindings or guards.

Co-authored-by: Deadbeef <ent3rm4n@gmail.com>
This commit is contained in:
Nadrieril 2024-03-09 03:29:52 +01:00
parent a61b14d15e
commit c623319a30
11 changed files with 259 additions and 39 deletions

View File

@ -711,13 +711,19 @@ fn cat_pattern_<F>(
self.cat_pattern_(place_with_id, subpat, op)?; 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 // box p1, &p1, &mut p1. we can ignore the mutability of
// PatKind::Ref since that information is already contained // PatKind::Ref since that information is already contained
// in the type. // in the type.
let subplace = self.cat_deref(pat, place_with_id)?; let subplace = self.cat_deref(pat, place_with_id)?;
self.cat_pattern_(subplace, subpat, op)?; 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) => { PatKind::Slice(before, ref slice, after) => {
let Some(element_ty) = place_with_id.place.ty().builtin_index() else { let Some(element_ty) = place_with_id.place.ty().builtin_index() else {

View File

@ -1163,6 +1163,7 @@ enum TestCase<'pat, 'tcx> {
Constant { value: mir::Const<'tcx> }, Constant { value: mir::Const<'tcx> },
Range(&'pat PatRange<'tcx>), Range(&'pat PatRange<'tcx>),
Slice { len: usize, variable_length: bool }, Slice { len: usize, variable_length: bool },
Deref { temp: Place<'tcx> },
Or { pats: Box<[FlatPat<'pat, 'tcx>]> }, Or { pats: Box<[FlatPat<'pat, 'tcx>]> },
} }
@ -1222,6 +1223,12 @@ enum TestKind<'tcx> {
/// Test that the length of the slice is equal to `len`. /// Test that the length of the slice is equal to `len`.
Len { len: u64, op: BinOp }, 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. /// A test to perform to determine which [`Candidate`] matches a value.

View File

@ -42,6 +42,8 @@ pub(super) fn test<'pat>(&mut self, match_pair: &MatchPair<'pat, 'tcx>) -> Test<
TestKind::Len { len: len as u64, op } TestKind::Len { len: len as u64, op }
} }
TestCase::Deref { temp } => TestKind::Deref { temp },
TestCase::Or { .. } => bug!("or-patterns should have already been handled"), TestCase::Or { .. } => bug!("or-patterns should have already been handled"),
TestCase::Irrefutable { .. } => span_bug!( TestCase::Irrefutable { .. } => span_bug!(
@ -143,35 +145,11 @@ pub(super) fn perform_test(
); );
} }
let re_erased = tcx.lifetimes.re_erased; 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_ty = Ty::new_imm_ref(tcx, re_erased, tcx.types.str_);
let ref_str = self.temp(ref_str_ty, test.span); 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(); let eq_block = self.cfg.start_new_block();
self.cfg.push_assign( // `let ref_str: &str = <String as Deref>::deref(&place);`
block, self.call_deref(block, eq_block, place, ty, ref_str, test.span);
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,
},
);
self.non_scalar_compare( self.non_scalar_compare(
eq_block, eq_block,
success_block, success_block,
@ -270,8 +248,56 @@ pub(super) fn perform_test(
Operand::Move(expected), 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 /// Compare using the provided built-in comparison operator
fn compare( fn compare(
@ -660,13 +686,21 @@ pub(super) fn sort_candidate(
} }
} }
(TestKind::Deref { temp: test_temp }, TestCase::Deref { temp })
if test_temp == temp =>
{
fully_matched = true;
Some(TestBranch::Success)
}
( (
TestKind::Switch { .. } TestKind::Switch { .. }
| TestKind::SwitchInt { .. } | TestKind::SwitchInt { .. }
| TestKind::If | TestKind::If
| TestKind::Len { .. } | TestKind::Len { .. }
| TestKind::Range { .. } | TestKind::Range { .. }
| TestKind::Eq { .. }, | TestKind::Eq { .. }
| TestKind::Deref { .. },
_, _,
) => { ) => {
fully_matched = false; fully_matched = false;

View File

@ -5,8 +5,8 @@
use rustc_infer::infer::type_variable::TypeVariableOrigin; use rustc_infer::infer::type_variable::TypeVariableOrigin;
use rustc_middle::mir::*; use rustc_middle::mir::*;
use rustc_middle::thir::{self, *}; use rustc_middle::thir::{self, *};
use rustc_middle::ty;
use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::TypeVisitableExt;
use rustc_middle::ty::{self, Ty};
impl<'a, 'tcx> Builder<'a, 'tcx> { impl<'a, 'tcx> Builder<'a, 'tcx> {
pub(crate) fn field_match_pairs<'pat>( pub(crate) fn field_match_pairs<'pat>(
@ -249,10 +249,15 @@ pub(in crate::build) fn new(
default_irrefutable() default_irrefutable()
} }
PatKind::DerefPattern { .. } => { PatKind::DerefPattern { ref subpattern } => {
// FIXME(deref_patterns) // Create a new temporary for each deref pattern.
// Treat it like a wildcard for now. // FIXME(deref_patterns): dedup temporaries to avoid multiple `deref()` calls?
default_irrefutable() 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 }
} }
}; };

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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() {}

View File

@ -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`.

View File

@ -4,6 +4,8 @@
use std::rc::Rc; use std::rc::Rc;
struct Struct;
fn main() { fn main() {
let vec: Vec<u32> = Vec::new(); let vec: Vec<u32> = Vec::new();
match vec { match vec {
@ -22,10 +24,12 @@ fn main() {
deref!(1..) => {} deref!(1..) => {}
_ => {} _ => {}
} }
// FIXME(deref_patterns): fails to typecheck because `"foo"` has type &str but deref creates a let _: &Struct = match &Rc::new(Struct) {
// place of type `str`. deref!(x) => x,
// match "foo".to_string() { _ => unreachable!(),
// box "foo" => {} };
// _ => {} let _: &[Struct] = match &Rc::new(vec![Struct]) {
// } deref!(deref!(x)) => x,
_ => unreachable!(),
};
} }

View File

@ -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
_ => {}
}
}

View File

@ -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`.