1 //! MIR lowering for patterns
3 use hir_def
::{hir::LiteralOrConst, resolver::HasResolver, AssocItemId}
;
5 use crate::BindingMode
;
9 macro_rules
! not_supported
{
11 return Err(MirLowerError
::NotSupported(format
!($x
)))
15 pub(super) enum AdtPatternShape
<'a
> {
16 Tuple { args: &'a [PatId], ellipsis: Option<usize> }
,
17 Record { args: &'a [RecordFieldPat] }
,
21 /// We need to do pattern matching in two phases: One to check if the pattern matches, and one to fill the bindings
22 /// of patterns. This is necessary to prevent double moves and similar problems. For example:
26 /// (b, 2) | (b, 3) => {},
30 /// If we do everything in one pass, we will move `X` to the first `b`, then we see that the second field of tuple
31 /// 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
32 /// it might even doesn't match the second pattern and we may want to not move `X` at all.
33 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
35 /// Check that if this pattern matches
37 /// Assume that this pattern matches, fill bindings
41 impl MirLowerCtx
<'_
> {
42 /// It gets a `current` unterminated block, appends some statements and possibly a terminator to it to check if
43 /// the pattern matches and write bindings, and returns two unterminated blocks, one for the matched path (which
44 /// can be the `current` block) and one for the mismatched path. If the input pattern is irrefutable, the
45 /// mismatched path block is `None`.
47 /// By default, it will create a new block for mismatched path. If you already have one, you can provide it with
48 /// `current_else` argument to save an unnecessary jump. If `current_else` isn't `None`, the result mismatched path
49 /// wouldn't be `None` as well. Note that this function will add jumps to the beginning of the `current_else` block,
50 /// so it should be an empty block.
51 pub(super) fn pattern_match(
53 current
: BasicBlockId
,
54 current_else
: Option
<BasicBlockId
>,
57 ) -> Result
<(BasicBlockId
, Option
<BasicBlockId
>)> {
58 let (current
, current_else
) = self.pattern_match_inner(
65 let (current
, current_else
) = self.pattern_match_inner(
72 Ok((current
, current_else
))
75 fn pattern_match_inner(
77 mut current
: BasicBlockId
,
78 mut current_else
: Option
<BasicBlockId
>,
79 mut cond_place
: Place
,
82 ) -> Result
<(BasicBlockId
, Option
<BasicBlockId
>)> {
83 let cnt
= self.infer
.pat_adjustments
.get(&pattern
).map(|x
| x
.len()).unwrap_or_default();
84 cond_place
.projection
= self.result
.projection_store
.intern(
87 .lookup(&self.result
.projection_store
)
90 .chain((0..cnt
).map(|_
| ProjectionElem
::Deref
))
94 Ok(match &self.body
.pats
[pattern
] {
95 Pat
::Missing
=> return Err(MirLowerError
::IncompletePattern
),
96 Pat
::Wild
=> (current
, current_else
),
97 Pat
::Tuple { args, ellipsis }
=> {
98 let subst
= match self.infer
[pattern
].kind(Interner
) {
99 TyKind
::Tuple(_
, s
) => s
,
101 return Err(MirLowerError
::TypeError(
102 "non tuple type matched with tuple pattern",
106 self.pattern_match_tuple_like(
111 (0..subst
.len(Interner
)).map(|i
| PlaceElem
::TupleOrClosureField(i
)),
117 let then_target
= self.new_basic_block();
118 let mut finished
= false;
120 let (mut next
, next_else
) = self.pattern_match_inner(
123 (&mut cond_place
).clone(),
127 if mode
== MatchingMode
::Bind
{
128 (next
, _
) = self.pattern_match_inner(
131 (&mut cond_place
).clone(),
136 self.set_goto(next
, then_target
, pattern
.into());
148 if mode
== MatchingMode
::Bind
{
149 self.set_terminator(current
, TerminatorKind
::Unreachable
, pattern
.into());
151 let ce
= *current_else
.get_or_insert_with(|| self.new_basic_block());
152 self.set_goto(current
, ce
, pattern
.into());
155 (then_target
, current_else
)
157 Pat
::Record { args, .. }
=> {
158 let Some(variant
) = self.infer
.variant_resolution_for_pat(pattern
) else {
159 not_supported
!("unresolved variant for record");
161 self.pattern_matching_variant(
167 AdtPatternShape
::Record { args: &*args }
,
171 Pat
::Range { start, end }
=> {
172 let mut add_check
= |l
: &LiteralOrConst
, binop
| -> Result
<()> {
174 self.lower_literal_or_const_to_operand(self.infer
[pattern
].clone(), l
)?
;
175 let else_target
= *current_else
.get_or_insert_with(|| self.new_basic_block());
176 let next
= self.new_basic_block();
178 self.temp(TyBuilder
::bool(), current
, pattern
.into())?
.into();
179 self.push_assignment(
182 Rvalue
::CheckedBinaryOp(
185 Operand
::Copy((&mut cond_place
).clone()),
189 let discr
= Operand
::Copy(discr
);
192 TerminatorKind
::SwitchInt
{
194 targets
: SwitchTargets
::static_if(1, next
, else_target
),
201 if mode
== MatchingMode
::Check
{
202 if let Some(start
) = start
{
203 add_check(start
, BinOp
::Le
)?
;
205 if let Some(end
) = end
{
206 add_check(end
, BinOp
::Ge
)?
;
209 (current
, current_else
)
211 Pat
::Slice { prefix, slice, suffix }
=> {
212 if mode
== MatchingMode
::Check
{
213 // emit runtime length check for slice
214 if let TyKind
::Slice(_
) = self.infer
[pattern
].kind(Interner
) {
215 let pattern_len
= prefix
.len() + suffix
.len();
216 let place_len
: Place
=
217 self.temp(TyBuilder
::usize(), current
, pattern
.into())?
.into();
218 self.push_assignment(
221 Rvalue
::Len((&mut cond_place
).clone()),
225 *current_else
.get_or_insert_with(|| self.new_basic_block());
226 let next
= self.new_basic_block();
230 TerminatorKind
::SwitchInt
{
231 discr
: Operand
::Copy(place_len
),
232 targets
: SwitchTargets
::static_if(
241 let c
= Operand
::from_concrete_const(
242 pattern_len
.to_le_bytes().to_vec(),
243 MemoryMap
::default(),
247 self.temp(TyBuilder
::bool(), current
, pattern
.into())?
.into();
248 self.push_assignment(
251 Rvalue
::CheckedBinaryOp(BinOp
::Le
, c
, Operand
::Copy(place_len
)),
254 let discr
= Operand
::Copy(discr
);
257 TerminatorKind
::SwitchInt
{
259 targets
: SwitchTargets
::static_if(1, next
, else_target
),
267 for (i
, &pat
) in prefix
.iter().enumerate() {
268 let next_place
= (&mut cond_place
).project(
269 ProjectionElem
::ConstantIndex { offset: i as u64, from_end: false }
,
270 &mut self.result
.projection_store
,
272 (current
, current_else
) =
273 self.pattern_match_inner(current
, current_else
, next_place
, pat
, mode
)?
;
275 if let Some(slice
) = slice
{
276 if mode
== MatchingMode
::Bind
{
277 if let Pat
::Bind { id, subpat: _ }
= self.body
[*slice
] {
278 let next_place
= (&mut cond_place
).project(
279 ProjectionElem
::Subslice
{
280 from
: prefix
.len() as u64,
281 to
: suffix
.len() as u64,
283 &mut self.result
.projection_store
,
285 (current
, current_else
) = self.pattern_match_binding(
295 for (i
, &pat
) in suffix
.iter().enumerate() {
296 let next_place
= (&mut cond_place
).project(
297 ProjectionElem
::ConstantIndex { offset: i as u64, from_end: true }
,
298 &mut self.result
.projection_store
,
300 (current
, current_else
) =
301 self.pattern_match_inner(current
, current_else
, next_place
, pat
, mode
)?
;
303 (current
, current_else
)
305 Pat
::Path(p
) => match self.infer
.variant_resolution_for_pat(pattern
) {
306 Some(variant
) => self.pattern_matching_variant(
312 AdtPatternShape
::Unit
,
316 // The path is not a variant, so it is a const
317 if mode
!= MatchingMode
::Check
{
318 // A const don't bind anything. Only needs check.
319 return Ok((current
, current_else
));
321 let unresolved_name
= || MirLowerError
::unresolved_path(self.db
, p
);
322 let resolver
= self.owner
.resolver(self.db
.upcast());
324 .resolve_path_in_value_ns(self.db
.upcast(), p
)
325 .ok_or_else(unresolved_name
)?
;
326 let (c
, subst
) = 'b
: {
327 if let Some(x
) = self.infer
.assoc_resolutions_for_pat(pattern
) {
328 if let AssocItemId
::ConstId(c
) = x
.0 {
332 if let ResolveValueResult
::ValueNs(v
, _
) = pr
{
333 if let ValueNs
::ConstId(c
) = v
{
334 break '
b (c
, Substitution
::empty(Interner
));
337 not_supported
!("path in pattern position that is not const or variant")
340 self.temp(self.infer
[pattern
].clone(), current
, pattern
.into())?
.into();
341 let span
= pattern
.into();
348 self.infer
[pattern
].clone(),
350 let tmp2
: Place
= self.temp(TyBuilder
::bool(), current
, pattern
.into())?
.into();
351 self.push_assignment(
354 Rvalue
::CheckedBinaryOp(
357 Operand
::Copy(cond_place
),
361 let next
= self.new_basic_block();
362 let else_target
= current_else
.unwrap_or_else(|| self.new_basic_block());
365 TerminatorKind
::SwitchInt
{
366 discr
: Operand
::Copy(tmp2
),
367 targets
: SwitchTargets
::static_if(1, next
, else_target
),
371 (next
, Some(else_target
))
374 Pat
::Lit(l
) => match &self.body
.exprs
[*l
] {
375 Expr
::Literal(l
) => {
376 if mode
== MatchingMode
::Check
{
377 let c
= self.lower_literal_to_operand(self.infer
[pattern
].clone(), l
)?
;
378 self.pattern_match_const(current_else
, current
, c
, cond_place
, pattern
)?
380 (current
, current_else
)
383 _
=> not_supported
!("expression path literal"),
385 Pat
::Bind { id, subpat }
=> {
386 if let Some(subpat
) = subpat
{
387 (current
, current_else
) = self.pattern_match_inner(
390 (&mut cond_place
).clone(),
395 if mode
== MatchingMode
::Bind
{
396 self.pattern_match_binding(
404 (current
, current_else
)
407 Pat
::TupleStruct { path: _, args, ellipsis }
=> {
408 let Some(variant
) = self.infer
.variant_resolution_for_pat(pattern
) else {
409 not_supported
!("unresolved variant");
411 self.pattern_matching_variant(
417 AdtPatternShape
::Tuple { args, ellipsis: *ellipsis }
,
421 Pat
::Ref { pat, mutability: _ }
=> {
423 cond_place
.project(ProjectionElem
::Deref
, &mut self.result
.projection_store
);
424 self.pattern_match_inner(current
, current_else
, cond_place
, *pat
, mode
)?
426 Pat
::Box { .. }
=> not_supported
!("box pattern"),
427 Pat
::ConstBlock(_
) => not_supported
!("const block pattern"),
431 fn pattern_match_binding(
436 current
: BasicBlockId
,
437 current_else
: Option
<BasicBlockId
>,
438 ) -> Result
<(BasicBlockId
, Option
<BasicBlockId
>)> {
439 let target_place
= self.binding_local(id
)?
;
440 let mode
= self.infer
.binding_modes
[id
];
441 self.push_storage_live(id
, current
)?
;
442 self.push_assignment(
446 BindingMode
::Move
=> Operand
::Copy(cond_place
).into(),
447 BindingMode
::Ref(Mutability
::Not
) => Rvalue
::Ref(BorrowKind
::Shared
, cond_place
),
448 BindingMode
::Ref(Mutability
::Mut
) => {
449 Rvalue
::Ref(BorrowKind
::Mut { allow_two_phase_borrow: false }
, cond_place
)
454 Ok((current
, current_else
))
457 fn pattern_match_const(
459 current_else
: Option
<BasicBlockId
>,
460 current
: BasicBlockId
,
464 ) -> Result
<(BasicBlockId
, Option
<BasicBlockId
>)> {
465 let then_target
= self.new_basic_block();
466 let else_target
= current_else
.unwrap_or_else(|| self.new_basic_block());
467 let discr
: Place
= self.temp(TyBuilder
::bool(), current
, pattern
.into())?
.into();
468 self.push_assignment(
471 Rvalue
::CheckedBinaryOp(BinOp
::Eq
, c
, Operand
::Copy(cond_place
)),
474 let discr
= Operand
::Copy(discr
);
477 TerminatorKind
::SwitchInt
{
479 targets
: SwitchTargets
::static_if(1, then_target
, else_target
),
483 Ok((then_target
, Some(else_target
)))
486 fn pattern_matching_variant(
490 mut current
: BasicBlockId
,
492 mut current_else
: Option
<BasicBlockId
>,
493 shape
: AdtPatternShape
<'_
>,
495 ) -> Result
<(BasicBlockId
, Option
<BasicBlockId
>)> {
497 VariantId
::EnumVariantId(v
) => {
498 if mode
== MatchingMode
::Check
{
499 let e
= self.const_eval_discriminant(v
)?
as u128
;
500 let tmp
= self.discr_temp_place(current
);
501 self.push_assignment(
504 Rvalue
::Discriminant(cond_place
.clone()),
507 let next
= self.new_basic_block();
508 let else_target
= current_else
.get_or_insert_with(|| self.new_basic_block());
511 TerminatorKind
::SwitchInt
{
512 discr
: Operand
::Copy(tmp
),
513 targets
: SwitchTargets
::static_if(e
, next
, *else_target
),
519 let enum_data
= self.db
.enum_data(v
.parent
);
520 self.pattern_matching_variant_fields(
522 &enum_data
.variants
[v
.local_id
].variant_data
,
530 VariantId
::StructId(s
) => {
531 let struct_data
= self.db
.struct_data(s
);
532 self.pattern_matching_variant_fields(
534 &struct_data
.variant_data
,
542 VariantId
::UnionId(_
) => {
543 return Err(MirLowerError
::TypeError("pattern matching on union"))
548 fn pattern_matching_variant_fields(
550 shape
: AdtPatternShape
<'_
>,
551 variant_data
: &VariantData
,
553 current
: BasicBlockId
,
554 current_else
: Option
<BasicBlockId
>,
557 ) -> Result
<(BasicBlockId
, Option
<BasicBlockId
>)> {
559 AdtPatternShape
::Record { args }
=> {
564 variant_data
.field(&x
.name
).ok_or(MirLowerError
::UnresolvedField
)?
;
566 PlaceElem
::Field(FieldId { parent: v.into(), local_id: field_id }
),
570 .collect
::<Result
<Vec
<_
>>>()?
;
571 self.pattern_match_adt(current
, current_else
, it
.into_iter(), cond_place
, mode
)?
573 AdtPatternShape
::Tuple { args, ellipsis }
=> {
574 let fields
= variant_data
577 .map(|(x
, _
)| PlaceElem
::Field(FieldId { parent: v.into(), local_id: x }
));
578 self.pattern_match_tuple_like(
588 AdtPatternShape
::Unit
=> (current
, current_else
),
592 fn pattern_match_adt(
594 mut current
: BasicBlockId
,
595 mut current_else
: Option
<BasicBlockId
>,
596 args
: impl Iterator
<Item
= (PlaceElem
, PatId
)>,
599 ) -> Result
<(BasicBlockId
, Option
<BasicBlockId
>)> {
600 for (proj
, arg
) in args
{
601 let cond_place
= cond_place
.project(proj
, &mut self.result
.projection_store
);
602 (current
, current_else
) =
603 self.pattern_match_inner(current
, current_else
, cond_place
, arg
, mode
)?
;
605 Ok((current
, current_else
))
608 fn pattern_match_tuple_like(
610 current
: BasicBlockId
,
611 current_else
: Option
<BasicBlockId
>,
613 ellipsis
: Option
<usize>,
614 fields
: impl DoubleEndedIterator
<Item
= PlaceElem
> + Clone
,
617 ) -> Result
<(BasicBlockId
, Option
<BasicBlockId
>)> {
618 let (al
, ar
) = args
.split_at(ellipsis
.unwrap_or(args
.len()));
622 .chain(ar
.iter().rev().zip(fields
.rev()))
623 .map(|(x
, y
)| (y
, *x
));
624 self.pattern_match_adt(current
, current_else
, it
, cond_place
, mode
)