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, PlaceRef, 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: PlaceRef<'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: PlaceRef<'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: PlaceRef<'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 cond_place = cond_place.project(ProjectionElem::Deref);
495 self.pattern_match_inner(current, current_else, cond_place, *pat, mode)?
496 }
497 &Pat::Expr(expr) => {
498 stdx::always!(
499 mode == MatchingMode::Assign,
500 "Pat::Expr can only come in destructuring assignments"
501 );
502 let Some((lhs_place, current)) = self.lower_expr_as_place(current, expr, false)?
503 else {
504 return Ok((current, current_else));
505 };
506 self.push_assignment(
507 current,
508 lhs_place,
509 Operand { kind: OperandKind::Copy(cond_place.store()), span: None }.into(),
510 expr.into(),
511 );
512 (current, current_else)
513 }
514 Pat::Box { .. } => not_supported!("box pattern"),
515 Pat::Deref { .. } => not_supported!("deref pattern"),
516 Pat::ConstBlock(_) => not_supported!("const block pattern"),
517 })
518 }
519
520 fn pattern_match_binding(
521 &mut self,
522 id: BindingId,
523 mode: BindingMode,
524 cond_place: PlaceRef<'db>,
525 span: MirSpan,
526 current: BasicBlockId,
527 current_else: Option<BasicBlockId>,
528 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
529 let target_place = self.binding_local(id)?;
530 self.push_storage_live(id, current)?;
531 self.push_match_assignment(current, target_place, mode, cond_place, span);
532 Ok((current, current_else))
533 }
534
535 fn push_match_assignment(
536 &mut self,
537 current: BasicBlockId,
538 target_place: LocalId,
539 mode: BindingMode,
540 cond_place: PlaceRef<'db>,
541 span: MirSpan,
542 ) {
543 self.push_assignment(
544 current,
545 target_place.into(),
546 match mode {
547 BindingMode(ByRef::No, _) => {
548 Operand { kind: OperandKind::Copy(cond_place.store()), span: None }.into()
549 }
550 BindingMode(ByRef::Yes(rustc_ast_ir::Mutability::Not), _) => {
551 Rvalue::Ref(BorrowKind::Shared, cond_place.store())
552 }
553 BindingMode(ByRef::Yes(rustc_ast_ir::Mutability::Mut), _) => Rvalue::Ref(
554 BorrowKind::Mut { kind: MutBorrowKind::Default },
555 cond_place.store(),
556 ),
557 },
558 span,
559 );
560 }
561
562 fn pattern_match_const(
563 &mut self,
564 current_else: Option<BasicBlockId>,
565 current: BasicBlockId,
566 c: Operand,
567 cond_place: PlaceRef<'db>,
568 pattern: Idx<Pat>,
569 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
570 let then_target = self.new_basic_block();
571 let else_target = current_else.unwrap_or_else(|| self.new_basic_block());
572 let discr = self.temp(Ty::new_bool(self.interner()), current, pattern.into())?.into();
573 self.push_assignment(
574 current,
575 discr,
576 Rvalue::CheckedBinaryOp(
577 BinOp::Eq,
578 c,
579 Operand { kind: OperandKind::Copy(cond_place.store()), span: None },
580 ),
581 pattern.into(),
582 );
583 let discr = Operand { kind: OperandKind::Copy(discr.store()), span: None };
584 self.set_terminator(
585 current,
586 TerminatorKind::SwitchInt {
587 discr,
588 targets: SwitchTargets::static_if(1, then_target, else_target),
589 },
590 pattern.into(),
591 );
592 Ok((then_target, Some(else_target)))
593 }
594
595 fn pattern_matching_variant(
596 &mut self,
597 cond_place: PlaceRef<'db>,
598 variant: VariantId,
599 mut current: BasicBlockId,
600 span: MirSpan,
601 mut current_else: Option<BasicBlockId>,
602 shape: AdtPatternShape<'_>,
603 mode: MatchingMode,
604 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
605 let place_ty = cond_place.ty(&self.result, &self.infcx, self.env).ty;
606 let Some((place_adt, _)) = place_ty.as_adt() else {
607 return Err(MirLowerError::TypeError("non ADT type matched with ADT pattern"));
608 };
609 if place_adt != variant.adt_id(self.db) {
610 return Err(MirLowerError::TypeError("ADT pattern does not match place type"));
611 }
612
613 Ok(match variant {
614 VariantId::EnumVariantId(v) => {
615 if mode == MatchingMode::Check {
616 let e = self.const_eval_discriminant(v)? as u128;
617 let tmp = self.discr_temp_place(current);
618 self.push_assignment(
619 current,
620 tmp,
621 Rvalue::Discriminant(cond_place.store()),
622 span,
623 );
624 let next = self.new_basic_block();
625 let else_target = current_else.get_or_insert_with(|| self.new_basic_block());
626 self.set_terminator(
627 current,
628 TerminatorKind::SwitchInt {
629 discr: Operand { kind: OperandKind::Copy(tmp.store()), span: None },
630 targets: SwitchTargets::static_if(e, next, *else_target),
631 },
632 span,
633 );
634 current = next;
635 }
636 self.pattern_matching_variant_fields(
637 shape,
638 v.fields(self.db),
639 variant,
640 current,
641 current_else,
642 &cond_place,
643 mode,
644 )?
645 }
646 VariantId::StructId(s) => self.pattern_matching_variant_fields(
647 shape,
648 s.fields(self.db),
649 variant,
650 current,
651 current_else,
652 &cond_place,
653 mode,
654 )?,
655 VariantId::UnionId(_) => {
656 return Err(MirLowerError::TypeError("pattern matching on union"));
657 }
658 })
659 }
660
661 fn pattern_matching_variant_fields(
662 &mut self,
663 shape: AdtPatternShape<'_>,
664 variant_data: &VariantFields,
665 v: VariantId,
666 current: BasicBlockId,
667 current_else: Option<BasicBlockId>,
668 cond_place: &PlaceRef<'db>,
669 mode: MatchingMode,
670 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
671 let downcast_place = if matches!(v, VariantId::EnumVariantId(_)) {
672 cond_place.project(ProjectionElem::Downcast(v))
673 } else {
674 *cond_place
675 };
676 Ok(match shape {
677 AdtPatternShape::Record { args } => {
678 let it = args
679 .iter()
680 .map(|x| {
681 let field_id =
682 variant_data.field(&x.name).ok_or(MirLowerError::UnresolvedField)?;
683 Ok((PlaceElem::Field(field_id.into()), x.pat))
684 })
685 .collect::<Result<'db, Vec<_>>>()?;
686 self.pattern_match_adt(
687 current,
688 current_else,
689 it.into_iter(),
690 &downcast_place,
691 mode,
692 )?
693 }
694 AdtPatternShape::Tuple { args, ellipsis } => {
695 let fields = variant_data.fields().iter().map(|(x, _)| PlaceElem::Field(x.into()));
696 self.pattern_match_tuple_like(
697 current,
698 current_else,
699 args,
700 ellipsis,
701 fields,
702 &downcast_place,
703 mode,
704 )?
705 }
706 AdtPatternShape::Unit => (current, current_else),
707 })
708 }
709
710 fn pattern_match_adt(
711 &mut self,
712 mut current: BasicBlockId,
713 mut current_else: Option<BasicBlockId>,
714 args: impl Iterator<Item = (PlaceElem, PatId)>,
715 cond_place: &PlaceRef<'db>,
716 mode: MatchingMode,
717 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
718 for (proj, arg) in args {
719 let cond_place = cond_place.project(proj);
720 (current, current_else) =
721 self.pattern_match_inner(current, current_else, cond_place, arg, mode)?;
722 }
723 Ok((current, current_else))
724 }
725
726 fn pattern_match_tuple_like(
727 &mut self,
728 current: BasicBlockId,
729 current_else: Option<BasicBlockId>,
730 args: &[PatId],
731 ellipsis: Option<u32>,
732 fields: impl DoubleEndedIterator<Item = PlaceElem> + Clone,
733 cond_place: &PlaceRef<'db>,
734 mode: MatchingMode,
735 ) -> Result<'db, (BasicBlockId, Option<BasicBlockId>)> {
736 let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize));
737 let it = al
738 .iter()
739 .zip(fields.clone())
740 .chain(ar.iter().rev().zip(fields.rev()))
741 .map(|(x, y)| (y, *x));
742 self.pattern_match_adt(current, current_else, it, cond_place, mode)
743 }
744}