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