1use hir_def::{
4 hir::{ExprId, RecordFieldPat},
5 signatures::VariantFields,
6};
7use rustc_type_ir::inherent::{IntoKind, Ty as _};
8
9use crate::{
10 BindingMode, ByRef,
11 mir::{
12 FieldIndex, LocalId, MutBorrowKind, Operand, OperandKind, Place, Projection,
13 lower::{
14 BasicBlockId, BinOp, BindingId, BorrowKind, Expr, Idx, MemoryMap, MirLowerCtx,
15 MirLowerError, MirSpan, Pat, PatId, PlaceElem, ProjectionElem, ResolveValueResult,
16 Result, Rvalue, SwitchTargets, TerminatorKind, Ty, TyKind, ValueNs, VariantId,
17 },
18 },
19};
20use crate::{method_resolution::CandidateId, next_solver::GenericArgs};
21
22macro_rules! not_supported {
23 ($x: expr) => {
24 return Err(MirLowerError::NotSupported(format!($x)))
25 };
26}
27
28pub(super) enum AdtPatternShape<'a> {
29 Tuple { args: &'a [PatId], ellipsis: Option<u32> },
30 Record { args: &'a [RecordFieldPat] },
31 Unit,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47enum MatchingMode {
48 Check,
50 Bind,
52 Assign,
54}
55
56impl<'db> MirLowerCtx<'_, 'db> {
57 pub(super) fn pattern_match(
67 &mut self,
68 current: BasicBlockId,
69 current_else: Option<BasicBlockId>,
70 cond_place: Place<'db>,
71 pattern: PatId,
72 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
73 let (current, current_else) = self.pattern_match_inner(
74 current,
75 current_else,
76 cond_place,
77 pattern,
78 MatchingMode::Check,
79 )?;
80 let (current, current_else) = self.pattern_match_inner(
81 current,
82 current_else,
83 cond_place,
84 pattern,
85 MatchingMode::Bind,
86 )?;
87 Ok((current, current_else))
88 }
89
90 pub(super) fn pattern_match_assignment(
91 &mut self,
92 current: BasicBlockId,
93 value: Place<'db>,
94 pattern: PatId,
95 ) -> Result<'db, BasicBlockId> {
96 let (current, _) =
97 self.pattern_match_inner(current, None, value, pattern, MatchingMode::Assign)?;
98 Ok(current)
99 }
100
101 pub(super) fn match_self_param(
102 &mut self,
103 id: BindingId,
104 current: BasicBlockId,
105 local: LocalId,
106 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
107 self.pattern_match_binding(
108 id,
109 BindingMode(ByRef::No, rustc_ast_ir::Mutability::Not),
110 local.into(),
111 MirSpan::SelfParam,
112 current,
113 None,
114 )
115 }
116
117 fn pattern_match_inner(
118 &mut self,
119 mut current: BasicBlockId,
120 mut current_else: Option<BasicBlockId>,
121 mut cond_place: Place<'db>,
122 pattern: PatId,
123 mode: MatchingMode,
124 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
125 let cnt = self.infer.pat_adjustments.get(&pattern).map(|x| x.len()).unwrap_or_default();
126 cond_place.projection = Projection::new_from_iter(
127 cond_place
128 .projection
129 .as_slice()
130 .iter()
131 .cloned()
132 .chain((0..cnt).map(|_| ProjectionElem::Deref)),
133 );
134 Ok(match &self.store[pattern] {
135 Pat::Missing | Pat::Rest | Pat::NotNull => {
136 return Err(MirLowerError::IncompletePattern);
137 }
138 Pat::Wild => (current, current_else),
139 Pat::Tuple { args, ellipsis } => {
140 let place_ty = cond_place.ty(&self.result, &self.infcx, self.env).ty;
141 let subst = match place_ty.kind() {
142 TyKind::Tuple(s) => s,
143 _ => {
144 return Err(MirLowerError::TypeError(
145 "non tuple type matched with tuple pattern",
146 ));
147 }
148 };
149 self.pattern_match_tuple_like(
150 current,
151 current_else,
152 args,
153 *ellipsis,
154 (0..subst.len()).map(|i| PlaceElem::Field(FieldIndex(i as u32))),
155 &cond_place,
156 mode,
157 )?
158 }
159 Pat::Or(pats) => {
160 let then_target = self.new_basic_block();
161 let mut finished = false;
162 for pat in &**pats {
163 let (mut next, next_else) = self.pattern_match_inner(
164 current,
165 None,
166 cond_place,
167 *pat,
168 MatchingMode::Check,
169 )?;
170 if mode != MatchingMode::Check {
171 (next, _) = self.pattern_match_inner(next, None, cond_place, *pat, mode)?;
172 }
173 self.set_goto(next, then_target, pattern.into());
174 match next_else {
175 Some(t) => {
176 current = t;
177 }
178 None => {
179 finished = true;
180 break;
181 }
182 }
183 }
184 if !finished {
185 if mode == MatchingMode::Check {
186 let ce = *current_else.get_or_insert_with(|| self.new_basic_block());
187 self.set_goto(current, ce, pattern.into());
188 } else {
189 self.set_terminator(current, TerminatorKind::Unreachable, pattern.into());
190 }
191 }
192 (then_target, current_else)
193 }
194 Pat::Record { args, .. } => {
195 let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else {
196 not_supported!("unresolved variant for record");
197 };
198 self.pattern_matching_variant(
199 cond_place,
200 variant,
201 current,
202 pattern.into(),
203 current_else,
204 AdtPatternShape::Record { args },
205 mode,
206 )?
207 }
208 Pat::Range { start, end, range_type: _ } => {
209 let mut add_check = |l: &ExprId, binop| -> Result<'db, ()> {
210 let lv =
211 self.lower_literal_or_const_to_operand(self.infer.pat_ty(pattern), l)?;
212 let else_target = *current_else.get_or_insert_with(|| self.new_basic_block());
213 let next = self.new_basic_block();
214 let discr =
215 self.temp(Ty::new_bool(self.interner()), current, pattern.into())?.into();
216 self.push_assignment(
217 current,
218 discr,
219 Rvalue::CheckedBinaryOp(
220 binop,
221 lv,
222 Operand { kind: OperandKind::Copy(cond_place.store()), span: None },
223 ),
224 pattern.into(),
225 );
226 let discr = Operand { kind: OperandKind::Copy(discr.store()), span: None };
227 self.set_terminator(
228 current,
229 TerminatorKind::SwitchInt {
230 discr,
231 targets: SwitchTargets::static_if(1, next, else_target),
232 },
233 pattern.into(),
234 );
235 current = next;
236 Ok(())
237 };
238 if mode == MatchingMode::Check {
239 if let Some(start) = start {
240 add_check(start, BinOp::Le)?;
241 }
242 if let Some(end) = end {
243 add_check(end, BinOp::Ge)?;
244 }
245 }
246 (current, current_else)
247 }
248 Pat::Slice { prefix, slice, suffix } => {
249 let pat_ty = self.infer.pat_ty(pattern);
250 if !matches!(pat_ty.kind(), TyKind::Array(..) | TyKind::Slice(_)) {
253 return Err(MirLowerError::TypeError(
254 "non array or slice type matched with slice pattern",
255 ));
256 }
257
258 if mode == MatchingMode::Check {
259 if let TyKind::Slice(_) = pat_ty.kind() {
261 let pattern_len = prefix.len() + suffix.len();
262 let place_len = self
263 .temp(Ty::new_usize(self.interner()), current, pattern.into())?
264 .into();
265 self.push_assignment(
266 current,
267 place_len,
268 Rvalue::Len(cond_place.store()),
269 pattern.into(),
270 );
271 let else_target =
272 *current_else.get_or_insert_with(|| self.new_basic_block());
273 let next = self.new_basic_block();
274 if slice.is_none() {
275 self.set_terminator(
276 current,
277 TerminatorKind::SwitchInt {
278 discr: Operand {
279 kind: OperandKind::Copy(place_len.store()),
280 span: None,
281 },
282 targets: SwitchTargets::static_if(
283 pattern_len as u128,
284 next,
285 else_target,
286 ),
287 },
288 pattern.into(),
289 );
290 } else {
291 let c = Operand::from_concrete_const(
292 pattern_len.to_le_bytes().into(),
293 MemoryMap::default(),
294 Ty::new_usize(self.interner()),
295 );
296 let discr = self
297 .temp(Ty::new_bool(self.interner()), current, pattern.into())?
298 .into();
299 self.push_assignment(
300 current,
301 discr,
302 Rvalue::CheckedBinaryOp(
303 BinOp::Le,
304 c,
305 Operand {
306 kind: OperandKind::Copy(place_len.store()),
307 span: None,
308 },
309 ),
310 pattern.into(),
311 );
312 let discr =
313 Operand { kind: OperandKind::Copy(discr.store()), span: None };
314 self.set_terminator(
315 current,
316 TerminatorKind::SwitchInt {
317 discr,
318 targets: SwitchTargets::static_if(1, next, else_target),
319 },
320 pattern.into(),
321 );
322 }
323 current = next;
324 }
325 }
326 for (i, &pat) in prefix.iter().enumerate() {
327 let next_place = cond_place.project(ProjectionElem::ConstantIndex {
328 offset: i as u64,
329 from_end: false,
330 });
331 (current, current_else) =
332 self.pattern_match_inner(current, current_else, next_place, pat, mode)?;
333 }
334 if let &Some(slice) = slice
335 && mode != MatchingMode::Check
336 && let Pat::Bind { id, subpat: _ } = self.store[slice]
337 {
338 let next_place = cond_place.project(ProjectionElem::Subslice {
339 from: prefix.len() as u64,
340 to: suffix.len() as u64,
341 });
342 let mode = self.infer.binding_modes[slice];
343 (current, current_else) = self.pattern_match_binding(
344 id,
345 mode,
346 next_place,
347 (slice).into(),
348 current,
349 current_else,
350 )?;
351 }
352 for (i, &pat) in suffix.iter().enumerate() {
353 let next_place = cond_place.project(ProjectionElem::ConstantIndex {
354 offset: i as u64,
355 from_end: true,
356 });
357 (current, current_else) =
358 self.pattern_match_inner(current, current_else, next_place, pat, mode)?;
359 }
360 (current, current_else)
361 }
362 Pat::Path(p) => match self.infer.variant_resolution_for_pat(pattern) {
363 Some(variant) => self.pattern_matching_variant(
364 cond_place,
365 variant,
366 current,
367 pattern.into(),
368 current_else,
369 AdtPatternShape::Unit,
370 mode,
371 )?,
372 None => {
373 let unresolved_name = || {
374 MirLowerError::unresolved_path(
375 self.db,
376 p,
377 self.display_target(),
378 self.owner.expression_store_owner(self.db),
379 self.store,
380 )
381 };
382 let hygiene = self.store.pat_path_hygiene(pattern);
383 let pr = self
384 .resolver
385 .resolve_path_in_value_ns(self.db, p, hygiene)
386 .ok_or_else(unresolved_name)?;
387
388 if let (
389 MatchingMode::Assign,
390 ResolveValueResult::ValueNs(ValueNs::LocalBinding(binding)),
391 ) = (mode, &pr)
392 {
393 let local = self.binding_local(*binding)?;
394 self.push_match_assignment(
395 current,
396 local,
397 BindingMode(ByRef::No, rustc_ast_ir::Mutability::Not),
398 cond_place,
399 pattern.into(),
400 );
401 return Ok((current, current_else));
402 }
403
404 if mode != MatchingMode::Check {
406 return Ok((current, current_else));
408 }
409 let (c, subst) = 'b: {
410 if let Some(x) = self.infer.assoc_resolutions_for_pat(pattern)
411 && let CandidateId::ConstId(c) = x.0
412 {
413 break 'b (c, x.1);
414 }
415 if let ResolveValueResult::ValueNs(ValueNs::ConstId(c)) = pr {
416 break 'b (c, GenericArgs::empty(self.interner()));
417 }
418 not_supported!("path in pattern position that is not const or variant")
419 };
420 let tmp =
421 self.temp(self.infer.pat_ty(pattern), current, pattern.into())?.into();
422 let span = pattern.into();
423 self.lower_const(c.into(), current, tmp, subst, span)?;
424 let tmp2 =
425 self.temp(Ty::new_bool(self.interner()), current, pattern.into())?.into();
426 self.push_assignment(
427 current,
428 tmp2,
429 Rvalue::CheckedBinaryOp(
430 BinOp::Eq,
431 Operand { kind: OperandKind::Copy(tmp.store()), span: None },
432 Operand { kind: OperandKind::Copy(cond_place.store()), span: None },
433 ),
434 span,
435 );
436 let next = self.new_basic_block();
437 let else_target = current_else.unwrap_or_else(|| self.new_basic_block());
438 self.set_terminator(
439 current,
440 TerminatorKind::SwitchInt {
441 discr: Operand { kind: OperandKind::Copy(tmp2.store()), span: None },
442 targets: SwitchTargets::static_if(1, next, else_target),
443 },
444 span,
445 );
446 (next, Some(else_target))
447 }
448 },
449 Pat::Lit(l) => match &self.store[*l] {
450 Expr::Literal(l) => {
451 if mode == MatchingMode::Check {
452 let c = self.lower_literal_to_operand(self.infer.pat_ty(pattern), l)?;
453 self.pattern_match_const(current_else, current, c, cond_place, pattern)?
454 } else {
455 (current, current_else)
456 }
457 }
458 _ => not_supported!("expression path literal"),
459 },
460 Pat::Bind { id, subpat } => {
461 if let Some(subpat) = subpat {
462 (current, current_else) =
463 self.pattern_match_inner(current, current_else, cond_place, *subpat, mode)?
464 }
465 if mode != MatchingMode::Check {
466 let mode = self.infer.binding_modes[pattern];
467 self.pattern_match_binding(
468 *id,
469 mode,
470 cond_place,
471 pattern.into(),
472 current,
473 current_else,
474 )?
475 } else {
476 (current, current_else)
477 }
478 }
479 Pat::TupleStruct { path: _, args, ellipsis } => {
480 let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else {
481 not_supported!("unresolved variant");
482 };
483 self.pattern_matching_variant(
484 cond_place,
485 variant,
486 current,
487 pattern.into(),
488 current_else,
489 AdtPatternShape::Tuple { args, ellipsis: *ellipsis },
490 mode,
491 )?
492 }
493 Pat::Ref { pat, mutability: _ } => {
494 let ty = cond_place.ty(&self.result, &self.infcx, self.env).ty;
495 if !ty.is_ref() {
496 return Err(MirLowerError::TypeError(
497 "non reference type matched with reference pattern",
498 ));
499 }
500 let cond_place = cond_place.project(ProjectionElem::Deref);
501 self.pattern_match_inner(current, current_else, cond_place, *pat, mode)?
502 }
503 &Pat::Expr(expr) => {
504 stdx::always!(
505 mode == MatchingMode::Assign,
506 "Pat::Expr can only come in destructuring assignments"
507 );
508 let Some((lhs_place, current)) = self.lower_expr_as_place(current, expr, false)?
509 else {
510 return Ok((current, current_else));
511 };
512 self.push_assignment(
513 current,
514 lhs_place,
515 Operand { kind: OperandKind::Copy(cond_place.store()), span: None }.into(),
516 expr.into(),
517 );
518 (current, current_else)
519 }
520 Pat::Box { .. } => not_supported!("box pattern"),
521 Pat::Deref { .. } => not_supported!("deref pattern"),
522 })
523 }
524
525 fn pattern_match_binding(
526 &mut self,
527 id: BindingId,
528 mode: BindingMode,
529 cond_place: Place<'db>,
530 span: MirSpan,
531 current: BasicBlockId,
532 current_else: Option<BasicBlockId>,
533 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
534 let target_place = self.binding_local(id)?;
535 self.push_storage_live(id, current)?;
536 self.push_match_assignment(current, target_place, mode, cond_place, span);
537 Ok((current, current_else))
538 }
539
540 fn push_match_assignment(
541 &mut self,
542 current: BasicBlockId,
543 target_place: LocalId,
544 mode: BindingMode,
545 cond_place: Place<'db>,
546 span: MirSpan,
547 ) {
548 self.push_assignment(
549 current,
550 target_place.into(),
551 match mode {
552 BindingMode(ByRef::No, _) => {
553 Operand { kind: OperandKind::Copy(cond_place.store()), span: None }.into()
554 }
555 BindingMode(ByRef::Yes(rustc_ast_ir::Mutability::Not), _) => {
556 Rvalue::Ref(BorrowKind::Shared, cond_place.store())
557 }
558 BindingMode(ByRef::Yes(rustc_ast_ir::Mutability::Mut), _) => Rvalue::Ref(
559 BorrowKind::Mut { kind: MutBorrowKind::Default },
560 cond_place.store(),
561 ),
562 },
563 span,
564 );
565 }
566
567 fn pattern_match_const(
568 &mut self,
569 current_else: Option<BasicBlockId>,
570 current: BasicBlockId,
571 c: Operand,
572 cond_place: Place<'db>,
573 pattern: Idx<Pat>,
574 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
575 let then_target = self.new_basic_block();
576 let else_target = current_else.unwrap_or_else(|| self.new_basic_block());
577 let discr = self.temp(Ty::new_bool(self.interner()), current, pattern.into())?.into();
578 self.push_assignment(
579 current,
580 discr,
581 Rvalue::CheckedBinaryOp(
582 BinOp::Eq,
583 c,
584 Operand { kind: OperandKind::Copy(cond_place.store()), span: None },
585 ),
586 pattern.into(),
587 );
588 let discr = Operand { kind: OperandKind::Copy(discr.store()), span: None };
589 self.set_terminator(
590 current,
591 TerminatorKind::SwitchInt {
592 discr,
593 targets: SwitchTargets::static_if(1, then_target, else_target),
594 },
595 pattern.into(),
596 );
597 Ok((then_target, Some(else_target)))
598 }
599
600 fn pattern_matching_variant(
601 &mut self,
602 cond_place: Place<'db>,
603 variant: VariantId,
604 mut current: BasicBlockId,
605 span: MirSpan,
606 mut current_else: Option<BasicBlockId>,
607 shape: AdtPatternShape<'_>,
608 mode: MatchingMode,
609 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
610 let place_ty = cond_place.ty(&self.result, &self.infcx, self.env).ty;
611 let Some((place_adt, _)) = place_ty.as_adt() else {
612 return Err(MirLowerError::TypeError("non ADT type matched with ADT pattern"));
613 };
614 if place_adt != variant.adt_id(self.db) {
615 return Err(MirLowerError::TypeError("ADT pattern does not match place type"));
616 }
617
618 Ok(match variant {
619 VariantId::EnumVariantId(v) => {
620 if mode == MatchingMode::Check {
621 let e = self.const_eval_discriminant(v)? as u128;
622 let tmp = self.discr_temp_place(current);
623 self.push_assignment(
624 current,
625 tmp,
626 Rvalue::Discriminant(cond_place.store()),
627 span,
628 );
629 let next = self.new_basic_block();
630 let else_target = current_else.get_or_insert_with(|| self.new_basic_block());
631 self.set_terminator(
632 current,
633 TerminatorKind::SwitchInt {
634 discr: Operand { kind: OperandKind::Copy(tmp.store()), span: None },
635 targets: SwitchTargets::static_if(e, next, *else_target),
636 },
637 span,
638 );
639 current = next;
640 }
641 self.pattern_matching_variant_fields(
642 shape,
643 v.fields(self.db),
644 variant,
645 current,
646 current_else,
647 &cond_place,
648 mode,
649 )?
650 }
651 VariantId::StructId(s) => self.pattern_matching_variant_fields(
652 shape,
653 s.fields(self.db),
654 variant,
655 current,
656 current_else,
657 &cond_place,
658 mode,
659 )?,
660 VariantId::UnionId(_) => {
661 return Err(MirLowerError::TypeError("pattern matching on union"));
662 }
663 })
664 }
665
666 fn pattern_matching_variant_fields(
667 &mut self,
668 shape: AdtPatternShape<'_>,
669 variant_data: &VariantFields,
670 v: VariantId,
671 current: BasicBlockId,
672 current_else: Option<BasicBlockId>,
673 cond_place: &Place<'db>,
674 mode: MatchingMode,
675 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
676 let downcast_place = if matches!(v, VariantId::EnumVariantId(_)) {
677 cond_place.project(ProjectionElem::Downcast(v))
678 } else {
679 *cond_place
680 };
681 Ok(match shape {
682 AdtPatternShape::Record { args } => {
683 let it = args
684 .iter()
685 .map(|x| {
686 let field_id =
687 variant_data.field(&x.name).ok_or(MirLowerError::UnresolvedField)?;
688 Ok((PlaceElem::Field(field_id.into()), x.pat))
689 })
690 .collect::<Result<'db, Vec<_>>>()?;
691 self.pattern_match_adt(
692 current,
693 current_else,
694 it.into_iter(),
695 &downcast_place,
696 mode,
697 )?
698 }
699 AdtPatternShape::Tuple { args, ellipsis } => {
700 let fields = variant_data.fields().iter().map(|(x, _)| PlaceElem::Field(x.into()));
701 self.pattern_match_tuple_like(
702 current,
703 current_else,
704 args,
705 ellipsis,
706 fields,
707 &downcast_place,
708 mode,
709 )?
710 }
711 AdtPatternShape::Unit => (current, current_else),
712 })
713 }
714
715 fn pattern_match_adt(
716 &mut self,
717 mut current: BasicBlockId,
718 mut current_else: Option<BasicBlockId>,
719 args: impl Iterator<Item = (PlaceElem, PatId)>,
720 cond_place: &Place<'db>,
721 mode: MatchingMode,
722 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
723 for (proj, arg) in args {
724 let cond_place = cond_place.project(proj);
725 (current, current_else) =
726 self.pattern_match_inner(current, current_else, cond_place, arg, mode)?;
727 }
728 Ok((current, current_else))
729 }
730
731 fn pattern_match_tuple_like(
732 &mut self,
733 current: BasicBlockId,
734 current_else: Option<BasicBlockId>,
735 args: &[PatId],
736 ellipsis: Option<u32>,
737 fields: impl DoubleEndedIterator<Item = PlaceElem> + Clone,
738 cond_place: &Place<'db>,
739 mode: MatchingMode,
740 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
741 let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize));
742 let it = al
743 .iter()
744 .zip(fields.clone())
745 .chain(ar.iter().rev().zip(fields.rev()))
746 .map(|(x, y)| (y, *x));
747 self.pattern_match_adt(current, current_else, it, cond_place, mode)
748 }
749}