mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
617 lines
25 KiB
Rust
617 lines
25 KiB
Rust
//! MIR lowering for patterns
|
|
|
|
use hir_def::{hir::LiteralOrConst, resolver::HasResolver, AssocItemId};
|
|
|
|
use crate::BindingMode;
|
|
|
|
use super::*;
|
|
|
|
macro_rules! not_supported {
|
|
($x: expr) => {
|
|
return Err(MirLowerError::NotSupported(format!($x)))
|
|
};
|
|
}
|
|
|
|
pub(super) enum AdtPatternShape<'a> {
|
|
Tuple { args: &'a [PatId], ellipsis: Option<usize> },
|
|
Record { args: &'a [RecordFieldPat] },
|
|
Unit,
|
|
}
|
|
|
|
/// We need to do pattern matching in two phases: One to check if the pattern matches, and one to fill the bindings
|
|
/// of patterns. This is necessary to prevent double moves and similar problems. For example:
|
|
/// ```ignore
|
|
/// struct X;
|
|
/// match (X, 3) {
|
|
/// (b, 2) | (b, 3) => {},
|
|
/// _ => {}
|
|
/// }
|
|
/// ```
|
|
/// If we do everything in one pass, we will move `X` to the first `b`, then we see that the second field of tuple
|
|
/// doesn't match and we should move the `X` to the second `b` (which here is the same thing, but doesn't need to be) and
|
|
/// it might even doesn't match the second pattern and we may want to not move `X` at all.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum MatchingMode {
|
|
/// Check that if this pattern matches
|
|
Check,
|
|
/// Assume that this pattern matches, fill bindings
|
|
Bind,
|
|
}
|
|
|
|
impl MirLowerCtx<'_> {
|
|
/// It gets a `current` unterminated block, appends some statements and possibly a terminator to it to check if
|
|
/// the pattern matches and write bindings, and returns two unterminated blocks, one for the matched path (which
|
|
/// can be the `current` block) and one for the mismatched path. If the input pattern is irrefutable, the
|
|
/// mismatched path block is `None`.
|
|
///
|
|
/// By default, it will create a new block for mismatched path. If you already have one, you can provide it with
|
|
/// `current_else` argument to save an unnecessary jump. If `current_else` isn't `None`, the result mismatched path
|
|
/// wouldn't be `None` as well. Note that this function will add jumps to the beginning of the `current_else` block,
|
|
/// so it should be an empty block.
|
|
pub(super) fn pattern_match(
|
|
&mut self,
|
|
current: BasicBlockId,
|
|
current_else: Option<BasicBlockId>,
|
|
cond_place: Place,
|
|
pattern: PatId,
|
|
) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
|
|
let (current, current_else) = self.pattern_match_inner(
|
|
current,
|
|
current_else,
|
|
cond_place,
|
|
pattern,
|
|
MatchingMode::Check,
|
|
)?;
|
|
let (current, current_else) = self.pattern_match_inner(
|
|
current,
|
|
current_else,
|
|
cond_place,
|
|
pattern,
|
|
MatchingMode::Bind,
|
|
)?;
|
|
Ok((current, current_else))
|
|
}
|
|
|
|
fn pattern_match_inner(
|
|
&mut self,
|
|
mut current: BasicBlockId,
|
|
mut current_else: Option<BasicBlockId>,
|
|
mut cond_place: Place,
|
|
pattern: PatId,
|
|
mode: MatchingMode,
|
|
) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
|
|
let cnt = self.infer.pat_adjustments.get(&pattern).map(|x| x.len()).unwrap_or_default();
|
|
cond_place.projection = self.result.projection_store.intern(
|
|
cond_place
|
|
.projection
|
|
.lookup(&self.result.projection_store)
|
|
.iter()
|
|
.cloned()
|
|
.chain((0..cnt).map(|_| ProjectionElem::Deref))
|
|
.collect::<Vec<_>>()
|
|
.into(),
|
|
);
|
|
Ok(match &self.body.pats[pattern] {
|
|
Pat::Missing => return Err(MirLowerError::IncompletePattern),
|
|
Pat::Wild => (current, current_else),
|
|
Pat::Tuple { args, ellipsis } => {
|
|
let subst = match self.infer[pattern].kind(Interner) {
|
|
TyKind::Tuple(_, s) => s,
|
|
_ => {
|
|
return Err(MirLowerError::TypeError(
|
|
"non tuple type matched with tuple pattern",
|
|
))
|
|
}
|
|
};
|
|
self.pattern_match_tuple_like(
|
|
current,
|
|
current_else,
|
|
args,
|
|
*ellipsis,
|
|
(0..subst.len(Interner)).map(|i| {
|
|
PlaceElem::Field(Either::Right(TupleFieldId {
|
|
tuple: TupleId(!0), // Dummy as it is unused
|
|
index: i as u32,
|
|
}))
|
|
}),
|
|
&cond_place,
|
|
mode,
|
|
)?
|
|
}
|
|
Pat::Or(pats) => {
|
|
let then_target = self.new_basic_block();
|
|
let mut finished = false;
|
|
for pat in &**pats {
|
|
let (mut next, next_else) = self.pattern_match_inner(
|
|
current,
|
|
None,
|
|
cond_place,
|
|
*pat,
|
|
MatchingMode::Check,
|
|
)?;
|
|
if mode == MatchingMode::Bind {
|
|
(next, _) = self.pattern_match_inner(
|
|
next,
|
|
None,
|
|
cond_place,
|
|
*pat,
|
|
MatchingMode::Bind,
|
|
)?;
|
|
}
|
|
self.set_goto(next, then_target, pattern.into());
|
|
match next_else {
|
|
Some(t) => {
|
|
current = t;
|
|
}
|
|
None => {
|
|
finished = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if !finished {
|
|
if mode == MatchingMode::Bind {
|
|
self.set_terminator(current, TerminatorKind::Unreachable, pattern.into());
|
|
} else {
|
|
let ce = *current_else.get_or_insert_with(|| self.new_basic_block());
|
|
self.set_goto(current, ce, pattern.into());
|
|
}
|
|
}
|
|
(then_target, current_else)
|
|
}
|
|
Pat::Record { args, .. } => {
|
|
let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else {
|
|
not_supported!("unresolved variant for record");
|
|
};
|
|
self.pattern_matching_variant(
|
|
cond_place,
|
|
variant,
|
|
current,
|
|
pattern.into(),
|
|
current_else,
|
|
AdtPatternShape::Record { args },
|
|
mode,
|
|
)?
|
|
}
|
|
Pat::Range { start, end } => {
|
|
let mut add_check = |l: &LiteralOrConst, binop| -> Result<()> {
|
|
let lv =
|
|
self.lower_literal_or_const_to_operand(self.infer[pattern].clone(), l)?;
|
|
let else_target = *current_else.get_or_insert_with(|| self.new_basic_block());
|
|
let next = self.new_basic_block();
|
|
let discr: Place =
|
|
self.temp(TyBuilder::bool(), current, pattern.into())?.into();
|
|
self.push_assignment(
|
|
current,
|
|
discr,
|
|
Rvalue::CheckedBinaryOp(binop, lv, Operand::Copy(cond_place)),
|
|
pattern.into(),
|
|
);
|
|
let discr = Operand::Copy(discr);
|
|
self.set_terminator(
|
|
current,
|
|
TerminatorKind::SwitchInt {
|
|
discr,
|
|
targets: SwitchTargets::static_if(1, next, else_target),
|
|
},
|
|
pattern.into(),
|
|
);
|
|
current = next;
|
|
Ok(())
|
|
};
|
|
if mode == MatchingMode::Check {
|
|
if let Some(start) = start {
|
|
add_check(start, BinOp::Le)?;
|
|
}
|
|
if let Some(end) = end {
|
|
add_check(end, BinOp::Ge)?;
|
|
}
|
|
}
|
|
(current, current_else)
|
|
}
|
|
Pat::Slice { prefix, slice, suffix } => {
|
|
if mode == MatchingMode::Check {
|
|
// emit runtime length check for slice
|
|
if let TyKind::Slice(_) = self.infer[pattern].kind(Interner) {
|
|
let pattern_len = prefix.len() + suffix.len();
|
|
let place_len: Place =
|
|
self.temp(TyBuilder::usize(), current, pattern.into())?.into();
|
|
self.push_assignment(
|
|
current,
|
|
place_len,
|
|
Rvalue::Len(cond_place),
|
|
pattern.into(),
|
|
);
|
|
let else_target =
|
|
*current_else.get_or_insert_with(|| self.new_basic_block());
|
|
let next = self.new_basic_block();
|
|
if slice.is_none() {
|
|
self.set_terminator(
|
|
current,
|
|
TerminatorKind::SwitchInt {
|
|
discr: Operand::Copy(place_len),
|
|
targets: SwitchTargets::static_if(
|
|
pattern_len as u128,
|
|
next,
|
|
else_target,
|
|
),
|
|
},
|
|
pattern.into(),
|
|
);
|
|
} else {
|
|
let c = Operand::from_concrete_const(
|
|
pattern_len.to_le_bytes().into(),
|
|
MemoryMap::default(),
|
|
TyBuilder::usize(),
|
|
);
|
|
let discr: Place =
|
|
self.temp(TyBuilder::bool(), current, pattern.into())?.into();
|
|
self.push_assignment(
|
|
current,
|
|
discr,
|
|
Rvalue::CheckedBinaryOp(BinOp::Le, c, Operand::Copy(place_len)),
|
|
pattern.into(),
|
|
);
|
|
let discr = Operand::Copy(discr);
|
|
self.set_terminator(
|
|
current,
|
|
TerminatorKind::SwitchInt {
|
|
discr,
|
|
targets: SwitchTargets::static_if(1, next, else_target),
|
|
},
|
|
pattern.into(),
|
|
);
|
|
}
|
|
current = next;
|
|
}
|
|
}
|
|
for (i, &pat) in prefix.iter().enumerate() {
|
|
let next_place = cond_place.project(
|
|
ProjectionElem::ConstantIndex { offset: i as u64, from_end: false },
|
|
&mut self.result.projection_store,
|
|
);
|
|
(current, current_else) =
|
|
self.pattern_match_inner(current, current_else, next_place, pat, mode)?;
|
|
}
|
|
if let Some(slice) = slice {
|
|
if mode == MatchingMode::Bind {
|
|
if let Pat::Bind { id, subpat: _ } = self.body[*slice] {
|
|
let next_place = cond_place.project(
|
|
ProjectionElem::Subslice {
|
|
from: prefix.len() as u64,
|
|
to: suffix.len() as u64,
|
|
},
|
|
&mut self.result.projection_store,
|
|
);
|
|
(current, current_else) = self.pattern_match_binding(
|
|
id,
|
|
*slice,
|
|
next_place,
|
|
(*slice).into(),
|
|
current,
|
|
current_else,
|
|
)?;
|
|
}
|
|
}
|
|
}
|
|
for (i, &pat) in suffix.iter().enumerate() {
|
|
let next_place = cond_place.project(
|
|
ProjectionElem::ConstantIndex { offset: i as u64, from_end: true },
|
|
&mut self.result.projection_store,
|
|
);
|
|
(current, current_else) =
|
|
self.pattern_match_inner(current, current_else, next_place, pat, mode)?;
|
|
}
|
|
(current, current_else)
|
|
}
|
|
Pat::Path(p) => match self.infer.variant_resolution_for_pat(pattern) {
|
|
Some(variant) => self.pattern_matching_variant(
|
|
cond_place,
|
|
variant,
|
|
current,
|
|
pattern.into(),
|
|
current_else,
|
|
AdtPatternShape::Unit,
|
|
mode,
|
|
)?,
|
|
None => {
|
|
// The path is not a variant, so it is a const
|
|
if mode != MatchingMode::Check {
|
|
// A const don't bind anything. Only needs check.
|
|
return Ok((current, current_else));
|
|
}
|
|
let unresolved_name = || MirLowerError::unresolved_path(self.db, p);
|
|
let resolver = self.owner.resolver(self.db.upcast());
|
|
let pr = resolver
|
|
.resolve_path_in_value_ns(self.db.upcast(), p)
|
|
.ok_or_else(unresolved_name)?;
|
|
let (c, subst) = 'b: {
|
|
if let Some(x) = self.infer.assoc_resolutions_for_pat(pattern) {
|
|
if let AssocItemId::ConstId(c) = x.0 {
|
|
break 'b (c, x.1);
|
|
}
|
|
}
|
|
if let ResolveValueResult::ValueNs(ValueNs::ConstId(c), _) = pr {
|
|
break 'b (c, Substitution::empty(Interner));
|
|
}
|
|
not_supported!("path in pattern position that is not const or variant")
|
|
};
|
|
let tmp: Place =
|
|
self.temp(self.infer[pattern].clone(), current, pattern.into())?.into();
|
|
let span = pattern.into();
|
|
self.lower_const(
|
|
c.into(),
|
|
current,
|
|
tmp,
|
|
subst,
|
|
span,
|
|
self.infer[pattern].clone(),
|
|
)?;
|
|
let tmp2: Place = self.temp(TyBuilder::bool(), current, pattern.into())?.into();
|
|
self.push_assignment(
|
|
current,
|
|
tmp2,
|
|
Rvalue::CheckedBinaryOp(
|
|
BinOp::Eq,
|
|
Operand::Copy(tmp),
|
|
Operand::Copy(cond_place),
|
|
),
|
|
span,
|
|
);
|
|
let next = self.new_basic_block();
|
|
let else_target = current_else.unwrap_or_else(|| self.new_basic_block());
|
|
self.set_terminator(
|
|
current,
|
|
TerminatorKind::SwitchInt {
|
|
discr: Operand::Copy(tmp2),
|
|
targets: SwitchTargets::static_if(1, next, else_target),
|
|
},
|
|
span,
|
|
);
|
|
(next, Some(else_target))
|
|
}
|
|
},
|
|
Pat::Lit(l) => match &self.body.exprs[*l] {
|
|
Expr::Literal(l) => {
|
|
if mode == MatchingMode::Check {
|
|
let c = self.lower_literal_to_operand(self.infer[pattern].clone(), l)?;
|
|
self.pattern_match_const(current_else, current, c, cond_place, pattern)?
|
|
} else {
|
|
(current, current_else)
|
|
}
|
|
}
|
|
_ => not_supported!("expression path literal"),
|
|
},
|
|
Pat::Bind { id, subpat } => {
|
|
if let Some(subpat) = subpat {
|
|
(current, current_else) =
|
|
self.pattern_match_inner(current, current_else, cond_place, *subpat, mode)?
|
|
}
|
|
if mode == MatchingMode::Bind {
|
|
self.pattern_match_binding(
|
|
*id,
|
|
pattern,
|
|
cond_place,
|
|
pattern.into(),
|
|
current,
|
|
current_else,
|
|
)?
|
|
} else {
|
|
(current, current_else)
|
|
}
|
|
}
|
|
Pat::TupleStruct { path: _, args, ellipsis } => {
|
|
let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else {
|
|
not_supported!("unresolved variant");
|
|
};
|
|
self.pattern_matching_variant(
|
|
cond_place,
|
|
variant,
|
|
current,
|
|
pattern.into(),
|
|
current_else,
|
|
AdtPatternShape::Tuple { args, ellipsis: *ellipsis },
|
|
mode,
|
|
)?
|
|
}
|
|
Pat::Ref { pat, mutability: _ } => {
|
|
let cond_place =
|
|
cond_place.project(ProjectionElem::Deref, &mut self.result.projection_store);
|
|
self.pattern_match_inner(current, current_else, cond_place, *pat, mode)?
|
|
}
|
|
Pat::Box { .. } => not_supported!("box pattern"),
|
|
Pat::ConstBlock(_) => not_supported!("const block pattern"),
|
|
})
|
|
}
|
|
|
|
fn pattern_match_binding(
|
|
&mut self,
|
|
id: BindingId,
|
|
pat: PatId,
|
|
cond_place: Place,
|
|
span: MirSpan,
|
|
current: BasicBlockId,
|
|
current_else: Option<BasicBlockId>,
|
|
) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
|
|
let target_place = self.binding_local(id)?;
|
|
let mode = self.infer.binding_modes[pat];
|
|
self.push_storage_live(id, current)?;
|
|
self.push_assignment(
|
|
current,
|
|
target_place.into(),
|
|
match mode {
|
|
BindingMode::Move => Operand::Copy(cond_place).into(),
|
|
BindingMode::Ref(Mutability::Not) => Rvalue::Ref(BorrowKind::Shared, cond_place),
|
|
BindingMode::Ref(Mutability::Mut) => {
|
|
Rvalue::Ref(BorrowKind::Mut { allow_two_phase_borrow: false }, cond_place)
|
|
}
|
|
},
|
|
span,
|
|
);
|
|
Ok((current, current_else))
|
|
}
|
|
|
|
fn pattern_match_const(
|
|
&mut self,
|
|
current_else: Option<BasicBlockId>,
|
|
current: BasicBlockId,
|
|
c: Operand,
|
|
cond_place: Place,
|
|
pattern: Idx<Pat>,
|
|
) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
|
|
let then_target = self.new_basic_block();
|
|
let else_target = current_else.unwrap_or_else(|| self.new_basic_block());
|
|
let discr: Place = self.temp(TyBuilder::bool(), current, pattern.into())?.into();
|
|
self.push_assignment(
|
|
current,
|
|
discr,
|
|
Rvalue::CheckedBinaryOp(BinOp::Eq, c, Operand::Copy(cond_place)),
|
|
pattern.into(),
|
|
);
|
|
let discr = Operand::Copy(discr);
|
|
self.set_terminator(
|
|
current,
|
|
TerminatorKind::SwitchInt {
|
|
discr,
|
|
targets: SwitchTargets::static_if(1, then_target, else_target),
|
|
},
|
|
pattern.into(),
|
|
);
|
|
Ok((then_target, Some(else_target)))
|
|
}
|
|
|
|
fn pattern_matching_variant(
|
|
&mut self,
|
|
cond_place: Place,
|
|
variant: VariantId,
|
|
mut current: BasicBlockId,
|
|
span: MirSpan,
|
|
mut current_else: Option<BasicBlockId>,
|
|
shape: AdtPatternShape<'_>,
|
|
mode: MatchingMode,
|
|
) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
|
|
Ok(match variant {
|
|
VariantId::EnumVariantId(v) => {
|
|
if mode == MatchingMode::Check {
|
|
let e = self.const_eval_discriminant(v)? as u128;
|
|
let tmp = self.discr_temp_place(current);
|
|
self.push_assignment(current, tmp, Rvalue::Discriminant(cond_place), span);
|
|
let next = self.new_basic_block();
|
|
let else_target = current_else.get_or_insert_with(|| self.new_basic_block());
|
|
self.set_terminator(
|
|
current,
|
|
TerminatorKind::SwitchInt {
|
|
discr: Operand::Copy(tmp),
|
|
targets: SwitchTargets::static_if(e, next, *else_target),
|
|
},
|
|
span,
|
|
);
|
|
current = next;
|
|
}
|
|
self.pattern_matching_variant_fields(
|
|
shape,
|
|
&self.db.enum_variant_data(v).variant_data,
|
|
variant,
|
|
current,
|
|
current_else,
|
|
&cond_place,
|
|
mode,
|
|
)?
|
|
}
|
|
VariantId::StructId(s) => self.pattern_matching_variant_fields(
|
|
shape,
|
|
&self.db.struct_data(s).variant_data,
|
|
variant,
|
|
current,
|
|
current_else,
|
|
&cond_place,
|
|
mode,
|
|
)?,
|
|
VariantId::UnionId(_) => {
|
|
return Err(MirLowerError::TypeError("pattern matching on union"))
|
|
}
|
|
})
|
|
}
|
|
|
|
fn pattern_matching_variant_fields(
|
|
&mut self,
|
|
shape: AdtPatternShape<'_>,
|
|
variant_data: &VariantData,
|
|
v: VariantId,
|
|
current: BasicBlockId,
|
|
current_else: Option<BasicBlockId>,
|
|
cond_place: &Place,
|
|
mode: MatchingMode,
|
|
) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
|
|
Ok(match shape {
|
|
AdtPatternShape::Record { args } => {
|
|
let it = args
|
|
.iter()
|
|
.map(|x| {
|
|
let field_id =
|
|
variant_data.field(&x.name).ok_or(MirLowerError::UnresolvedField)?;
|
|
Ok((
|
|
PlaceElem::Field(Either::Left(FieldId {
|
|
parent: v,
|
|
local_id: field_id,
|
|
})),
|
|
x.pat,
|
|
))
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
self.pattern_match_adt(current, current_else, it.into_iter(), cond_place, mode)?
|
|
}
|
|
AdtPatternShape::Tuple { args, ellipsis } => {
|
|
let fields = variant_data.fields().iter().map(|(x, _)| {
|
|
PlaceElem::Field(Either::Left(FieldId { parent: v, local_id: x }))
|
|
});
|
|
self.pattern_match_tuple_like(
|
|
current,
|
|
current_else,
|
|
args,
|
|
ellipsis,
|
|
fields,
|
|
cond_place,
|
|
mode,
|
|
)?
|
|
}
|
|
AdtPatternShape::Unit => (current, current_else),
|
|
})
|
|
}
|
|
|
|
fn pattern_match_adt(
|
|
&mut self,
|
|
mut current: BasicBlockId,
|
|
mut current_else: Option<BasicBlockId>,
|
|
args: impl Iterator<Item = (PlaceElem, PatId)>,
|
|
cond_place: &Place,
|
|
mode: MatchingMode,
|
|
) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
|
|
for (proj, arg) in args {
|
|
let cond_place = cond_place.project(proj, &mut self.result.projection_store);
|
|
(current, current_else) =
|
|
self.pattern_match_inner(current, current_else, cond_place, arg, mode)?;
|
|
}
|
|
Ok((current, current_else))
|
|
}
|
|
|
|
fn pattern_match_tuple_like(
|
|
&mut self,
|
|
current: BasicBlockId,
|
|
current_else: Option<BasicBlockId>,
|
|
args: &[PatId],
|
|
ellipsis: Option<usize>,
|
|
fields: impl DoubleEndedIterator<Item = PlaceElem> + Clone,
|
|
cond_place: &Place,
|
|
mode: MatchingMode,
|
|
) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
|
|
let (al, ar) = args.split_at(ellipsis.unwrap_or(args.len()));
|
|
let it = al
|
|
.iter()
|
|
.zip(fields.clone())
|
|
.chain(ar.iter().rev().zip(fields.rev()))
|
|
.map(|(x, y)| (y, *x));
|
|
self.pattern_match_adt(current, current_else, it, cond_place, mode)
|
|
}
|
|
}
|