rustc_mir_build/builder/matches/
test.rs

1// Testing candidates
2//
3// After candidates have been simplified, the only match pairs that
4// remain are those that require some sort of test. The functions here
5// identify what tests are needed, perform the tests, and then filter
6// the candidates based on the result.
7
8use std::sync::Arc;
9
10use rustc_data_structures::fx::FxIndexMap;
11use rustc_hir::{LangItem, RangeEnd};
12use rustc_middle::mir::*;
13use rustc_middle::ty::util::IntTypeExt;
14use rustc_middle::ty::{self, GenericArg, Ty, TyCtxt};
15use rustc_middle::{bug, span_bug};
16use rustc_span::def_id::DefId;
17use rustc_span::source_map::Spanned;
18use rustc_span::{DUMMY_SP, Span, Symbol, sym};
19use tracing::{debug, instrument};
20
21use crate::builder::Builder;
22use crate::builder::matches::{
23    MatchPairTree, PatConstKind, SliceLenOp, Test, TestBranch, TestKind, TestableCase,
24};
25
26impl<'a, 'tcx> Builder<'a, 'tcx> {
27    /// Identifies what test is needed to decide if `match_pair` is applicable.
28    ///
29    /// It is a bug to call this with a not-fully-simplified pattern.
30    pub(super) fn pick_test_for_match_pair(
31        &mut self,
32        match_pair: &MatchPairTree<'tcx>,
33    ) -> Test<'tcx> {
34        let kind = match match_pair.testable_case {
35            TestableCase::Variant { adt_def, variant_index: _ } => TestKind::Switch { adt_def },
36
37            TestableCase::Constant { value: _, kind: PatConstKind::Bool } => TestKind::If,
38            TestableCase::Constant { value: _, kind: PatConstKind::IntOrChar } => {
39                TestKind::SwitchInt
40            }
41            TestableCase::Constant { value, kind: PatConstKind::Float } => {
42                TestKind::Eq { value, cast_ty: match_pair.pattern_ty }
43            }
44            TestableCase::Constant { value, kind: PatConstKind::Other } => {
45                TestKind::Eq { value, cast_ty: match_pair.pattern_ty }
46            }
47
48            TestableCase::Range(ref range) => {
49                assert_eq!(range.ty, match_pair.pattern_ty);
50                TestKind::Range(Arc::clone(range))
51            }
52
53            TestableCase::Slice { len, op } => TestKind::SliceLen { len, op },
54
55            TestableCase::Deref { temp, mutability } => TestKind::Deref { temp, mutability },
56
57            TestableCase::Never => TestKind::Never,
58
59            // Or-patterns are not tested directly; instead they are expanded into subcandidates,
60            // which are then distinguished by testing whatever non-or patterns they contain.
61            TestableCase::Or { .. } => bug!("or-patterns should have already been handled"),
62        };
63
64        Test { span: match_pair.pattern_span, kind }
65    }
66
67    #[instrument(skip(self, target_blocks, place), level = "debug")]
68    pub(super) fn perform_test(
69        &mut self,
70        match_start_span: Span,
71        scrutinee_span: Span,
72        block: BasicBlock,
73        otherwise_block: BasicBlock,
74        place: Place<'tcx>,
75        test: &Test<'tcx>,
76        target_blocks: FxIndexMap<TestBranch<'tcx>, BasicBlock>,
77    ) {
78        let place_ty = place.ty(&self.local_decls, self.tcx);
79        debug!(?place, ?place_ty);
80        let target_block = |branch| target_blocks.get(&branch).copied().unwrap_or(otherwise_block);
81
82        let source_info = self.source_info(test.span);
83        match test.kind {
84            TestKind::Switch { adt_def } => {
85                let otherwise_block = target_block(TestBranch::Failure);
86                let switch_targets = SwitchTargets::new(
87                    adt_def.discriminants(self.tcx).filter_map(|(idx, discr)| {
88                        if let Some(&block) = target_blocks.get(&TestBranch::Variant(idx)) {
89                            Some((discr.val, block))
90                        } else {
91                            None
92                        }
93                    }),
94                    otherwise_block,
95                );
96                debug!("num_enum_variants: {}", adt_def.variants().len());
97                let discr_ty = adt_def.repr().discr_type().to_ty(self.tcx);
98                let discr = self.temp(discr_ty, test.span);
99                self.cfg.push_assign(
100                    block,
101                    self.source_info(scrutinee_span),
102                    discr,
103                    Rvalue::Discriminant(place),
104                );
105                self.cfg.terminate(
106                    block,
107                    self.source_info(match_start_span),
108                    TerminatorKind::SwitchInt {
109                        discr: Operand::Move(discr),
110                        targets: switch_targets,
111                    },
112                );
113            }
114
115            TestKind::SwitchInt => {
116                // The switch may be inexhaustive so we have a catch-all block
117                let otherwise_block = target_block(TestBranch::Failure);
118                let switch_targets = SwitchTargets::new(
119                    target_blocks.iter().filter_map(|(&branch, &block)| {
120                        if let TestBranch::Constant(value) = branch {
121                            let bits = value.to_leaf().to_bits_unchecked();
122                            Some((bits, block))
123                        } else {
124                            None
125                        }
126                    }),
127                    otherwise_block,
128                );
129                let terminator = TerminatorKind::SwitchInt {
130                    discr: Operand::Copy(place),
131                    targets: switch_targets,
132                };
133                self.cfg.terminate(block, self.source_info(match_start_span), terminator);
134            }
135
136            TestKind::If => {
137                let success_block = target_block(TestBranch::Success);
138                let fail_block = target_block(TestBranch::Failure);
139                let terminator =
140                    TerminatorKind::if_(Operand::Copy(place), success_block, fail_block);
141                self.cfg.terminate(block, self.source_info(match_start_span), terminator);
142            }
143
144            TestKind::Eq { value, mut cast_ty } => {
145                let tcx = self.tcx;
146                let success_block = target_block(TestBranch::Success);
147                let fail_block = target_block(TestBranch::Failure);
148
149                let mut expect_ty = value.ty;
150                let mut expect = self.literal_operand(test.span, Const::from_ty_value(tcx, value));
151
152                let mut place = place;
153                let mut block = block;
154                match cast_ty.kind() {
155                    ty::Str => {
156                        // String literal patterns may have type `str` if `deref_patterns` is
157                        // enabled, in order to allow `deref!("..."): String`. In this case, `value`
158                        // is of type `&str`, so we compare it to `&place`.
159                        if !tcx.features().deref_patterns() {
160                            span_bug!(
161                                test.span,
162                                "matching on `str` went through without enabling deref_patterns"
163                            );
164                        }
165                        let re_erased = tcx.lifetimes.re_erased;
166                        let ref_str_ty = Ty::new_imm_ref(tcx, re_erased, tcx.types.str_);
167                        let ref_place = self.temp(ref_str_ty, test.span);
168                        // `let ref_place: &str = &place;`
169                        self.cfg.push_assign(
170                            block,
171                            self.source_info(test.span),
172                            ref_place,
173                            Rvalue::Ref(re_erased, BorrowKind::Shared, place),
174                        );
175                        place = ref_place;
176                        cast_ty = ref_str_ty;
177                    }
178                    ty::Adt(def, _) if tcx.is_lang_item(def.did(), LangItem::String) => {
179                        if !tcx.features().string_deref_patterns() {
180                            span_bug!(
181                                test.span,
182                                "matching on `String` went through without enabling string_deref_patterns"
183                            );
184                        }
185                        let re_erased = tcx.lifetimes.re_erased;
186                        let ref_str_ty = Ty::new_imm_ref(tcx, re_erased, tcx.types.str_);
187                        let ref_str = self.temp(ref_str_ty, test.span);
188                        let eq_block = self.cfg.start_new_block();
189                        // `let ref_str: &str = <String as Deref>::deref(&place);`
190                        self.call_deref(
191                            block,
192                            eq_block,
193                            place,
194                            Mutability::Not,
195                            cast_ty,
196                            ref_str,
197                            test.span,
198                        );
199                        // Since we generated a `ref_str = <String as Deref>::deref(&place) -> eq_block` terminator,
200                        // we need to add all further statements to `eq_block`.
201                        // Similarly, the normal test code should be generated for the `&str`, instead of the `String`.
202                        block = eq_block;
203                        place = ref_str;
204                        cast_ty = ref_str_ty;
205                    }
206                    &ty::Pat(base, _) => {
207                        assert_eq!(cast_ty, value.ty);
208                        assert!(base.is_trivially_pure_clone_copy());
209
210                        let transmuted_place = self.temp(base, test.span);
211                        self.cfg.push_assign(
212                            block,
213                            self.source_info(scrutinee_span),
214                            transmuted_place,
215                            Rvalue::Cast(CastKind::Transmute, Operand::Copy(place), base),
216                        );
217
218                        let transmuted_expect = self.temp(base, test.span);
219                        self.cfg.push_assign(
220                            block,
221                            self.source_info(test.span),
222                            transmuted_expect,
223                            Rvalue::Cast(CastKind::Transmute, expect, base),
224                        );
225
226                        place = transmuted_place;
227                        expect = Operand::Copy(transmuted_expect);
228                        cast_ty = base;
229                        expect_ty = base;
230                    }
231                    _ => {}
232                }
233
234                assert_eq!(expect_ty, cast_ty);
235                if !cast_ty.is_scalar() {
236                    // Use `PartialEq::eq` instead of `BinOp::Eq`
237                    // (the binop can only handle primitives)
238                    // Make sure that we do *not* call any user-defined code here.
239                    // The only type that can end up here is string literals, which have their
240                    // comparison defined in `core`.
241                    // (Interestingly this means that exhaustiveness analysis relies, for soundness,
242                    // on the `PartialEq` impl for `str` to b correct!)
243                    match *cast_ty.kind() {
244                        ty::Ref(_, deref_ty, _) if deref_ty == self.tcx.types.str_ => {}
245                        _ => {
246                            span_bug!(
247                                source_info.span,
248                                "invalid type for non-scalar compare: {cast_ty}"
249                            )
250                        }
251                    };
252                    self.string_compare(
253                        block,
254                        success_block,
255                        fail_block,
256                        source_info,
257                        expect,
258                        Operand::Copy(place),
259                    );
260                } else {
261                    self.compare(
262                        block,
263                        success_block,
264                        fail_block,
265                        source_info,
266                        BinOp::Eq,
267                        expect,
268                        Operand::Copy(place),
269                    );
270                }
271            }
272
273            TestKind::Range(ref range) => {
274                let success = target_block(TestBranch::Success);
275                let fail = target_block(TestBranch::Failure);
276                // Test `val` by computing `lo <= val && val <= hi`, using primitive comparisons.
277                let val = Operand::Copy(place);
278
279                let intermediate_block = if !range.lo.is_finite() {
280                    block
281                } else if !range.hi.is_finite() {
282                    success
283                } else {
284                    self.cfg.start_new_block()
285                };
286
287                if let Some(lo) = range.lo.as_finite() {
288                    let lo = ty::Value { ty: range.ty, valtree: lo };
289                    let lo = self.literal_operand(test.span, Const::from_ty_value(self.tcx, lo));
290                    self.compare(
291                        block,
292                        intermediate_block,
293                        fail,
294                        source_info,
295                        BinOp::Le,
296                        lo,
297                        val.clone(),
298                    );
299                };
300
301                if let Some(hi) = range.hi.as_finite() {
302                    let hi = ty::Value { ty: range.ty, valtree: hi };
303                    let hi = self.literal_operand(test.span, Const::from_ty_value(self.tcx, hi));
304                    let op = match range.end {
305                        RangeEnd::Included => BinOp::Le,
306                        RangeEnd::Excluded => BinOp::Lt,
307                    };
308                    self.compare(intermediate_block, success, fail, source_info, op, val, hi);
309                }
310            }
311
312            TestKind::SliceLen { len, op } => {
313                let usize_ty = self.tcx.types.usize;
314                let actual = self.temp(usize_ty, test.span);
315
316                // actual = len(place)
317                let length_op = self.len_of_slice_or_array(block, place, test.span, source_info);
318                self.cfg.push_assign(block, source_info, actual, Rvalue::Use(length_op));
319
320                // expected = <N>
321                let expected = self.push_usize(block, source_info, len);
322
323                let success_block = target_block(TestBranch::Success);
324                let fail_block = target_block(TestBranch::Failure);
325                // result = actual == expected OR result = actual < expected
326                // branch based on result
327                self.compare(
328                    block,
329                    success_block,
330                    fail_block,
331                    source_info,
332                    match op {
333                        SliceLenOp::Equal => BinOp::Eq,
334                        SliceLenOp::GreaterOrEqual => BinOp::Ge,
335                    },
336                    Operand::Move(actual),
337                    Operand::Move(expected),
338                );
339            }
340
341            TestKind::Deref { temp, mutability } => {
342                let ty = place_ty.ty;
343                let target = target_block(TestBranch::Success);
344                self.call_deref(block, target, place, mutability, ty, temp, test.span);
345            }
346
347            TestKind::Never => {
348                // Check that the place is initialized.
349                // FIXME(never_patterns): Also assert validity of the data at `place`.
350                self.cfg.push_fake_read(
351                    block,
352                    source_info,
353                    FakeReadCause::ForMatchedPlace(None),
354                    place,
355                );
356                // A never pattern is only allowed on an uninhabited type, so validity of the data
357                // implies unreachability.
358                self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
359            }
360        }
361    }
362
363    /// Perform `let temp = <ty as Deref>::deref(&place)`.
364    /// or `let temp = <ty as DerefMut>::deref_mut(&mut place)`.
365    pub(super) fn call_deref(
366        &mut self,
367        block: BasicBlock,
368        target_block: BasicBlock,
369        place: Place<'tcx>,
370        mutability: Mutability,
371        ty: Ty<'tcx>,
372        temp: Place<'tcx>,
373        span: Span,
374    ) {
375        let (trait_item, method) = match mutability {
376            Mutability::Not => (LangItem::Deref, sym::deref),
377            Mutability::Mut => (LangItem::DerefMut, sym::deref_mut),
378        };
379        let borrow_kind = super::util::ref_pat_borrow_kind(mutability);
380        let source_info = self.source_info(span);
381        let re_erased = self.tcx.lifetimes.re_erased;
382        let trait_item = self.tcx.require_lang_item(trait_item, span);
383        let method = trait_method(self.tcx, trait_item, method, [ty]);
384        let ref_src = self.temp(Ty::new_ref(self.tcx, re_erased, ty, mutability), span);
385        // `let ref_src = &src_place;`
386        // or `let ref_src = &mut src_place;`
387        self.cfg.push_assign(
388            block,
389            source_info,
390            ref_src,
391            Rvalue::Ref(re_erased, borrow_kind, place),
392        );
393        // `let temp = <Ty as Deref>::deref(ref_src);`
394        // or `let temp = <Ty as DerefMut>::deref_mut(ref_src);`
395        self.cfg.terminate(
396            block,
397            source_info,
398            TerminatorKind::Call {
399                func: Operand::Constant(Box::new(ConstOperand {
400                    span,
401                    user_ty: None,
402                    const_: method,
403                })),
404                args: [Spanned { node: Operand::Move(ref_src), span }].into(),
405                destination: temp,
406                target: Some(target_block),
407                unwind: UnwindAction::Continue,
408                call_source: CallSource::Misc,
409                fn_span: source_info.span,
410            },
411        );
412    }
413
414    /// Compare using the provided built-in comparison operator
415    fn compare(
416        &mut self,
417        block: BasicBlock,
418        success_block: BasicBlock,
419        fail_block: BasicBlock,
420        source_info: SourceInfo,
421        op: BinOp,
422        left: Operand<'tcx>,
423        right: Operand<'tcx>,
424    ) {
425        let bool_ty = self.tcx.types.bool;
426        let result = self.temp(bool_ty, source_info.span);
427
428        // result = op(left, right)
429        self.cfg.push_assign(
430            block,
431            source_info,
432            result,
433            Rvalue::BinaryOp(op, Box::new((left, right))),
434        );
435
436        // branch based on result
437        self.cfg.terminate(
438            block,
439            source_info,
440            TerminatorKind::if_(Operand::Move(result), success_block, fail_block),
441        );
442    }
443
444    /// Compare two values of type `&str` using `<str as std::cmp::PartialEq>::eq`.
445    fn string_compare(
446        &mut self,
447        block: BasicBlock,
448        success_block: BasicBlock,
449        fail_block: BasicBlock,
450        source_info: SourceInfo,
451        expect: Operand<'tcx>,
452        val: Operand<'tcx>,
453    ) {
454        let str_ty = self.tcx.types.str_;
455        let eq_def_id = self.tcx.require_lang_item(LangItem::PartialEq, source_info.span);
456        let method = trait_method(self.tcx, eq_def_id, sym::eq, [str_ty, str_ty]);
457
458        let bool_ty = self.tcx.types.bool;
459        let eq_result = self.temp(bool_ty, source_info.span);
460        let eq_block = self.cfg.start_new_block();
461        self.cfg.terminate(
462            block,
463            source_info,
464            TerminatorKind::Call {
465                func: Operand::Constant(Box::new(ConstOperand {
466                    span: source_info.span,
467
468                    // FIXME(#54571): This constant comes from user input (a
469                    // constant in a pattern). Are there forms where users can add
470                    // type annotations here?  For example, an associated constant?
471                    // Need to experiment.
472                    user_ty: None,
473
474                    const_: method,
475                })),
476                args: [
477                    Spanned { node: val, span: DUMMY_SP },
478                    Spanned { node: expect, span: DUMMY_SP },
479                ]
480                .into(),
481                destination: eq_result,
482                target: Some(eq_block),
483                unwind: UnwindAction::Continue,
484                call_source: CallSource::MatchCmp,
485                fn_span: source_info.span,
486            },
487        );
488        self.diverge_from(block);
489
490        // check the result
491        self.cfg.terminate(
492            eq_block,
493            source_info,
494            TerminatorKind::if_(Operand::Move(eq_result), success_block, fail_block),
495        );
496    }
497}
498
499fn trait_method<'tcx>(
500    tcx: TyCtxt<'tcx>,
501    trait_def_id: DefId,
502    method_name: Symbol,
503    args: impl IntoIterator<Item: Into<GenericArg<'tcx>>>,
504) -> Const<'tcx> {
505    // The unhygienic comparison here is acceptable because this is only
506    // used on known traits.
507    let item = tcx
508        .associated_items(trait_def_id)
509        .filter_by_name_unhygienic(method_name)
510        .find(|item| item.is_fn())
511        .expect("trait method not found");
512
513    let method_ty = Ty::new_fn_def(tcx, item.def_id, args);
514
515    Const::zero_sized(method_ty)
516}