diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index 8e7d3d0d9c656..220ab16a44d48 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -660,6 +660,14 @@ pub struct PatExtra<'tcx> { /// the pattern node back to the `DefId` of its original constant. pub expanded_const: Option, + /// If present, the original constant value that this array or slice + /// pattern node was expanded from by `const_to_pat`. + /// + /// Match lowering uses this to compare the scrutinee against the original + /// constant as a whole via `PartialEq::eq`, rather than element by + /// element. + pub expanded_const_value: Option>, + /// User-written types that must be preserved into MIR so that they can be /// checked. pub ascriptions: Vec>, diff --git a/compiler/rustc_mir_build/src/builder/matches/buckets.rs b/compiler/rustc_mir_build/src/builder/matches/buckets.rs index 0d2e9bf87585d..77f2a938f2b56 100644 --- a/compiler/rustc_mir_build/src/builder/matches/buckets.rs +++ b/compiler/rustc_mir_build/src/builder/matches/buckets.rs @@ -323,6 +323,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { value: case_val, kind: PatConstKind::Float | PatConstKind::Other, }, + ) + | ( + TestKind::AggregateEq { value: test_val, .. }, + TestableCase::Constant { value: case_val, kind: PatConstKind::Aggregate }, ) => { if test_val == case_val { fully_matched = true; @@ -353,6 +357,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | TestKind::Range { .. } | TestKind::StringEq { .. } | TestKind::ScalarEq { .. } + | TestKind::AggregateEq { .. } | TestKind::Deref { .. }, _, ) => { diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index b4ce8149f5e4d..93e4ba874cea2 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -13,6 +13,40 @@ use crate::builder::matches::{ FlatPat, MatchPairTree, PatConstKind, PatternExtraData, SliceLenOp, TestableCase, }; +/// Below this length, an array or slice pattern is compared element by element +/// rather than as a single aggregate, since the per-element comparisons are +/// unlikely to be more expensive than a `PartialEq::eq` call. +const AGGREGATE_EQ_MIN_LEN: usize = 4; + +impl<'a, 'tcx> Builder<'a, 'tcx> { + /// Check if we can use aggregate `PartialEq::eq` comparisons for constant array/slice patterns. + /// This is not possible in const contexts, because `PartialEq` is not const-stable yet. + fn can_use_aggregate_eq(&self) -> bool { + let in_const_context = self.tcx.is_const_fn(self.def_id.to_def_id()) + || !self.tcx.hir_body_owner_kind(self.def_id).is_fn_or_closure(); + !in_const_context + } + + /// If the given array or slice pattern node was expanded from a constant + /// by `const_to_pat` and an aggregate comparison is both possible and + /// worthwhile, returns the original constant value, so that the scrutinee + /// can be compared against it as a whole via `PartialEq::eq`. + /// + /// Note that this deliberately does not apply to hand-written array or + /// slice patterns, which only ever match element by element. + fn aggregate_const_value( + &self, + pattern: &Pat<'tcx>, + element_count: usize, + ) -> Option> { + let value = pattern.extra.as_deref()?.expanded_const_value?; + if element_count < AGGREGATE_EQ_MIN_LEN || !self.can_use_aggregate_eq() { + return None; + } + Some(value) + } +} + /// For an array or slice pattern's subpatterns (prefix/slice/suffix), returns a list /// of those subpatterns, each paired with a suitably-projected [`PlaceBuilder`]. fn prefix_slice_suffix<'a, 'tcx>( @@ -344,10 +378,26 @@ impl<'tcx> InterPat<'tcx> { _ => None, }; if let Some(array_len) = array_len { - for (subplace, subpat) in - prefix_slice_suffix(&place_builder, Some(array_len), prefix, slice, suffix) - { - subpats.push(InterPat::lower_thir_pat(cx, subplace, subpat)); + // If this pattern was expanded from a constant, compare + // the whole array against that constant at once via + // `PartialEq::eq` rather than element by element. + if let Some(aggregate_value) = cx.aggregate_const_value(pattern, prefix.len()) { + debug_assert!(slice.is_none() && suffix.is_empty()); + Some(TestableCase::Constant { + value: aggregate_value, + kind: PatConstKind::Aggregate, + }) + } else { + for (subplace, subpat) in prefix_slice_suffix( + &place_builder, + Some(array_len), + prefix, + slice, + suffix, + ) { + subpats.push(InterPat::lower_thir_pat(cx, subplace, subpat)); + } + None } } else { // If the array length couldn't be determined, ignore the @@ -359,33 +409,57 @@ impl<'tcx> InterPat<'tcx> { pattern.ty ), ); + None } - - None } PatKind::Slice { ref prefix, ref slice, ref suffix } => { - for (subplace, subpat) in - prefix_slice_suffix(&place_builder, None, prefix, slice, suffix) - { - subpats.push(InterPat::lower_thir_pat(cx, subplace, subpat)); - } - - if prefix.is_empty() && slice.is_some() && suffix.is_empty() { - // A slice pattern shaped like `[..]` is irrefutable. - // It can match a slice of any length, so no length test is needed. - None - } else { - // Any other shape of slice pattern requires a length test. - // Slice patterns with a `..` subpattern require a minimum - // length; those without `..` require an exact length. + // If this pattern was expanded from a constant, compare the + // whole slice against that constant at once via + // `PartialEq::eq` after the length check, rather than + // element by element. + if let Some(aggregate_value) = cx.aggregate_const_value(pattern, prefix.len()) { + debug_assert!(slice.is_none() && suffix.is_empty()); + subpats.push(InterPat { + place, + testable_case: Some(TestableCase::Constant { + value: aggregate_value, + kind: PatConstKind::Aggregate, + }), + subpats: Vec::new(), + or_subpats: None, + ascriptions: Vec::new(), + binding: None, + pattern_span: pattern.span, + is_never: false, + }); Some(TestableCase::Slice { - len: u64::try_from(prefix.len() + suffix.len()).unwrap(), - op: if slice.is_some() { - SliceLenOp::GreaterOrEqual - } else { - SliceLenOp::Equal - }, + len: u64::try_from(prefix.len()).unwrap(), + op: SliceLenOp::Equal, }) + } else { + for (subplace, subpat) in + prefix_slice_suffix(&place_builder, None, prefix, slice, suffix) + { + subpats.push(InterPat::lower_thir_pat(cx, subplace, subpat)); + } + + if prefix.is_empty() && slice.is_some() && suffix.is_empty() { + // A slice pattern shaped like `[..]` is irrefutable. + // It can match a slice of any length, so no length test is needed. + None + } else { + // Any other shape of slice pattern requires a length test. + // Slice patterns with a `..` subpattern require a minimum + // length; those without `..` require an exact length. + Some(TestableCase::Slice { + len: u64::try_from(prefix.len() + suffix.len()).unwrap(), + op: if slice.is_some() { + SliceLenOp::GreaterOrEqual + } else { + SliceLenOp::Equal + }, + }) + } } } diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 109f4de2698a4..54b561bef93b0 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -1248,6 +1248,10 @@ enum PatConstKind { Float, /// Constant string values, tested via string equality. String, + /// Constant array or slice values that array/slice patterns were expanded + /// from. Tested by calling `PartialEq::eq` on the whole aggregate at once, + /// rather than comparing element by element. + Aggregate, /// Any other constant-pattern is usually tested via some kind of equality /// check. Types that might be encountered here include: /// - raw pointers derived from integer values @@ -1333,6 +1337,10 @@ enum TestKind<'tcx> { /// Tests the place against a constant using scalar equality. ScalarEq { value: ty::Value<'tcx> }, + /// Tests the place against a constant array or slice using `PartialEq::eq`, + /// comparing the whole aggregate at once rather than element by element. + AggregateEq { value: ty::Value<'tcx> }, + /// Test whether the value falls within an inclusive or exclusive range. Range(Arc>), diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index 1c234bb8d70dc..21668df260b5f 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -40,6 +40,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { TestableCase::Constant { value, kind: PatConstKind::String } => { TestKind::StringEq { value } } + TestableCase::Constant { value, kind: PatConstKind::Aggregate } => { + TestKind::AggregateEq { value } + } TestableCase::Constant { value, kind: PatConstKind::Float | PatConstKind::Other } => { TestKind::ScalarEq { value } } @@ -137,27 +140,32 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.terminate(block, self.source_info(match_start_span), terminator); } - TestKind::StringEq { value } => { + TestKind::StringEq { value } | TestKind::AggregateEq { value } => { let tcx = self.tcx; let success_block = target_block(TestBranch::Success); let fail_block = target_block(TestBranch::Failure); - let ref_str_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, tcx.types.str_); - assert!(ref_str_ty.is_imm_ref_str(), "{ref_str_ty:?}"); - - // The string constant we're testing against has type `str`, but - // calling `::eq` requires `&str` operands. - // - // Because `str` and `&str` have the same valtree representation, - // we can "cast" to the desired type by just replacing the type. - assert!(value.ty.is_str(), "unexpected value type for StringEq test: {value:?}"); - let expected_value = ty::Value { ty: ref_str_ty, valtree: value.valtree }; + let inner_ty = value.ty; + if matches!(test.kind, TestKind::StringEq { .. }) { + assert!( + inner_ty.is_str(), + "unexpected value type for StringEq test: {value:?}" + ); + } + let ref_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, inner_ty); + + // The constant we're testing against has type `str`, `[T; N]`, or `[T]`, + // but calling `::eq` requires a reference operand + // (`&str`, `&[T; N]`, or `&[T]`). Valtree representations are the same + // with or without the reference wrapper, so we can "cast" to the + // desired type by just replacing the type. + let expected_value = ty::Value { ty: ref_ty, valtree: value.valtree }; let expected_value_operand = self.literal_operand(test.span, Const::from_ty_value(tcx, expected_value)); - // Similarly, the scrutinized place has type `str`, but we need `&str`. - // Get a reference by doing `let actual_value_ref_place: &str = &place`. - let actual_value_ref_place = self.temp(ref_str_ty, test.span); + // Similarly, the scrutinised place has the inner type, but we need a + // reference. Get one by doing `let actual_value_ref_place = &place`. + let actual_value_ref_place = self.temp(ref_ty, test.span); self.cfg.push_assign( block, self.source_info(test.span), @@ -165,16 +173,26 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { Rvalue::Ref(tcx.lifetimes.re_erased, BorrowKind::Shared, place), ); - // Compare two strings using `::eq`. - // (Interestingly this means that exhaustiveness analysis relies, for soundness, - // on the `PartialEq` impl for `str` to be correct!) - self.string_compare( + // Compare the two values using `::eq`. + // (Interestingly this means that, for `str`, exhaustiveness analysis + // relies for soundness on the `PartialEq` impl for `str` to be correct!) + // + // The aggregate comparisons, unlike the long-standing string ones, are + // asserted not to unwind, since an unwind edge would make + // borrow-checking stricter than for the `SwitchInt`s they replace. + // That is sound because a constant is only allowed in a pattern if its + // type is structural match, so the array/slice impl and every element + // impl it delegates to are derived or primitive, and cannot panic. + let can_unwind = matches!(test.kind, TestKind::StringEq { .. }); + self.non_scalar_compare( block, success_block, fail_block, source_info, + inner_ty, expected_value_operand, Operand::Copy(actual_value_ref_place), + can_unwind, ); } @@ -409,19 +427,31 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); } - /// Compare two values of type `&str` using `::eq`. - fn string_compare( + /// Compare two reference values using `::eq`. + /// + /// `compared_ty` is the *inner* type (e.g. `str`, `[u8; 64]`); + /// `expect` and `val` must already be references to that type. + /// + /// When `can_unwind` is false, the call is given `UnwindAction::Unreachable` + /// and no unwind edge, asserting that the `PartialEq::eq` implementation + /// cannot panic. This matters beyond codegen: an unwinding call would make + /// borrow-checking of the surrounding match stricter, because the unwind + /// path can create drop-order conflicts that the ordinary path does not + /// have. + fn non_scalar_compare( &mut self, block: BasicBlock, success_block: BasicBlock, fail_block: BasicBlock, source_info: SourceInfo, + compared_ty: Ty<'tcx>, expect: Operand<'tcx>, val: Operand<'tcx>, + can_unwind: bool, ) { - let str_ty = self.tcx.types.str_; let eq_def_id = self.tcx.require_lang_item(LangItem::PartialEq, source_info.span); - let method = trait_method(self.tcx, eq_def_id, sym::eq, &[str_ty.into(), str_ty.into()]); + let method = + trait_method(self.tcx, eq_def_id, sym::eq, &[compared_ty.into(), compared_ty.into()]); let bool_ty = self.tcx.types.bool; let eq_result = self.temp(bool_ty, source_info.span); @@ -448,12 +478,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { .into(), destination: eq_result, target: Some(eq_block), - unwind: UnwindAction::Continue, + unwind: if can_unwind { UnwindAction::Continue } else { UnwindAction::Unreachable }, call_source: CallSource::MatchCmp, fn_span: source_info.span, }, ); - self.diverge_from(block); + if can_unwind { + self.diverge_from(block); + } // check the result self.cfg.terminate( diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index 143df22452987..3e1a25ef5f14e 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -473,7 +473,15 @@ impl<'tcx> ConstToPat<'tcx> { } }; - Box::new(Pat { span, ty, kind, extra: None }) + let mut pat = Box::new(Pat { span, ty, kind, extra: None }); + if matches!(ty.kind(), ty::Array(..) | ty::Slice(_)) { + // Record the original constant value on array and slice nodes, so + // that match lowering can compare the scrutinee against the whole + // constant at once via `PartialEq::eq`, rather than element by + // element. + pat.extra.get_or_insert_default().expanded_const_value = Some(value); + } + pat } } diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index ddb56a04c308d..6b51f862840b3 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -703,10 +703,15 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { return; }; - let PatExtra { expanded_const, ascriptions } = extra; + let PatExtra { expanded_const, expanded_const_value, ascriptions } = extra; print_indented!(self, "extra: PatExtra {", depth_lvl); print_indented!(self, format_args!("expanded_const: {expanded_const:?}"), depth_lvl + 1); + print_indented!( + self, + format_args!("expanded_const_value: {expanded_const_value:?}"), + depth_lvl + 1 + ); self.print_list( "ascriptions", ascriptions, diff --git a/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-abort.mir new file mode 100644 index 0000000000000..97ca0c11c39a5 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-abort.mir @@ -0,0 +1,47 @@ +// MIR for `array_match` after built + +fn array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + let mut _2: &[u8; 4]; + let mut _3: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + _2 = &_1; + _3 = <[u8; 4] as PartialEq>::eq(copy _2, const &*b"\x01\x02\x03\x04") -> [return: bb4, unwind unreachable]; + } + + bb1: { + _0 = const false; + goto -> bb7; + } + + bb2: { + falseEdge -> [real: bb6, imaginary: bb1]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(move _3) -> [0: bb1, otherwise: bb2]; + } + + bb5: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb6: { + _0 = const true; + goto -> bb7; + } + + bb7: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..97ca0c11c39a5 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-unwind.mir @@ -0,0 +1,47 @@ +// MIR for `array_match` after built + +fn array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + let mut _2: &[u8; 4]; + let mut _3: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + _2 = &_1; + _3 = <[u8; 4] as PartialEq>::eq(copy _2, const &*b"\x01\x02\x03\x04") -> [return: bb4, unwind unreachable]; + } + + bb1: { + _0 = const false; + goto -> bb7; + } + + bb2: { + falseEdge -> [real: bb6, imaginary: bb1]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(move _3) -> [0: bb1, otherwise: bb2]; + } + + bb5: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb6: { + _0 = const true; + goto -> bb7; + } + + bb7: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-abort.mir new file mode 100644 index 0000000000000..c785ea537e9c9 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-abort.mir @@ -0,0 +1,64 @@ +// MIR for `const_array_match` after built + +fn const_array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + switchInt(copy _1[0 of 4]) -> [1: bb2, otherwise: bb1]; + } + + bb1: { + _0 = const false; + goto -> bb12; + } + + bb2: { + switchInt(copy _1[1 of 4]) -> [2: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy _1[2 of 4]) -> [3: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy _1[3 of 4]) -> [4: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb11: { + _0 = const true; + goto -> bb12; + } + + bb12: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..c785ea537e9c9 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-unwind.mir @@ -0,0 +1,64 @@ +// MIR for `const_array_match` after built + +fn const_array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + switchInt(copy _1[0 of 4]) -> [1: bb2, otherwise: bb1]; + } + + bb1: { + _0 = const false; + goto -> bb12; + } + + bb2: { + switchInt(copy _1[1 of 4]) -> [2: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy _1[2 of 4]) -> [3: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy _1[3 of 4]) -> [4: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb11: { + _0 = const true; + goto -> bb12; + } + + bb12: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-abort.mir new file mode 100644 index 0000000000000..4b105ec6d5d01 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-abort.mir @@ -0,0 +1,197 @@ +// MIR for `const_try_from_matched` after built + +fn const_try_from_matched(_1: [u8; 4]) -> Result { + debug value => _1; + let mut _0: std::result::Result; + let mut _2: &[u8; 4]; + let mut _3: MyEnum; + let mut _4: MyEnum; + let mut _5: MyEnum; + let mut _6: MyEnum; + let mut _7: (); + + bb0: { + StorageLive(_2); + _2 = &_1; + PlaceMention(_2); + switchInt(copy (*_2)[0 of 4]) -> [65: bb2, 69: bb10, 73: bb18, 77: bb26, otherwise: bb1]; + } + + bb1: { + StorageLive(_7); + _7 = (); + _0 = Result::::Err(move _7); + StorageDead(_7); + goto -> bb39; + } + + bb2: { + switchInt(copy (*_2)[1 of 4]) -> [66: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy (*_2)[2 of 4]) -> [67: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy (*_2)[3 of 4]) -> [68: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb38, imaginary: bb10]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + switchInt(copy (*_2)[1 of 4]) -> [70: bb12, otherwise: bb11]; + } + + bb11: { + goto -> bb1; + } + + bb12: { + switchInt(copy (*_2)[2 of 4]) -> [71: bb14, otherwise: bb13]; + } + + bb13: { + goto -> bb11; + } + + bb14: { + switchInt(copy (*_2)[3 of 4]) -> [72: bb16, otherwise: bb15]; + } + + bb15: { + goto -> bb13; + } + + bb16: { + falseEdge -> [real: bb37, imaginary: bb18]; + } + + bb17: { + goto -> bb15; + } + + bb18: { + switchInt(copy (*_2)[1 of 4]) -> [74: bb20, otherwise: bb19]; + } + + bb19: { + goto -> bb1; + } + + bb20: { + switchInt(copy (*_2)[2 of 4]) -> [75: bb22, otherwise: bb21]; + } + + bb21: { + goto -> bb19; + } + + bb22: { + switchInt(copy (*_2)[3 of 4]) -> [76: bb24, otherwise: bb23]; + } + + bb23: { + goto -> bb21; + } + + bb24: { + falseEdge -> [real: bb36, imaginary: bb26]; + } + + bb25: { + goto -> bb23; + } + + bb26: { + switchInt(copy (*_2)[1 of 4]) -> [78: bb28, otherwise: bb27]; + } + + bb27: { + goto -> bb1; + } + + bb28: { + switchInt(copy (*_2)[2 of 4]) -> [79: bb30, otherwise: bb29]; + } + + bb29: { + goto -> bb27; + } + + bb30: { + switchInt(copy (*_2)[3 of 4]) -> [80: bb32, otherwise: bb31]; + } + + bb31: { + goto -> bb29; + } + + bb32: { + falseEdge -> [real: bb35, imaginary: bb1]; + } + + bb33: { + goto -> bb31; + } + + bb34: { + FakeRead(ForMatchedPlace(None), _2); + unreachable; + } + + bb35: { + StorageLive(_6); + _6 = MyEnum::D; + _0 = Result::::Ok(move _6); + StorageDead(_6); + goto -> bb39; + } + + bb36: { + StorageLive(_5); + _5 = MyEnum::C; + _0 = Result::::Ok(move _5); + StorageDead(_5); + goto -> bb39; + } + + bb37: { + StorageLive(_4); + _4 = MyEnum::B; + _0 = Result::::Ok(move _4); + StorageDead(_4); + goto -> bb39; + } + + bb38: { + StorageLive(_3); + _3 = MyEnum::A; + _0 = Result::::Ok(move _3); + StorageDead(_3); + goto -> bb39; + } + + bb39: { + StorageDead(_2); + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..4b105ec6d5d01 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-unwind.mir @@ -0,0 +1,197 @@ +// MIR for `const_try_from_matched` after built + +fn const_try_from_matched(_1: [u8; 4]) -> Result { + debug value => _1; + let mut _0: std::result::Result; + let mut _2: &[u8; 4]; + let mut _3: MyEnum; + let mut _4: MyEnum; + let mut _5: MyEnum; + let mut _6: MyEnum; + let mut _7: (); + + bb0: { + StorageLive(_2); + _2 = &_1; + PlaceMention(_2); + switchInt(copy (*_2)[0 of 4]) -> [65: bb2, 69: bb10, 73: bb18, 77: bb26, otherwise: bb1]; + } + + bb1: { + StorageLive(_7); + _7 = (); + _0 = Result::::Err(move _7); + StorageDead(_7); + goto -> bb39; + } + + bb2: { + switchInt(copy (*_2)[1 of 4]) -> [66: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy (*_2)[2 of 4]) -> [67: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy (*_2)[3 of 4]) -> [68: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb38, imaginary: bb10]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + switchInt(copy (*_2)[1 of 4]) -> [70: bb12, otherwise: bb11]; + } + + bb11: { + goto -> bb1; + } + + bb12: { + switchInt(copy (*_2)[2 of 4]) -> [71: bb14, otherwise: bb13]; + } + + bb13: { + goto -> bb11; + } + + bb14: { + switchInt(copy (*_2)[3 of 4]) -> [72: bb16, otherwise: bb15]; + } + + bb15: { + goto -> bb13; + } + + bb16: { + falseEdge -> [real: bb37, imaginary: bb18]; + } + + bb17: { + goto -> bb15; + } + + bb18: { + switchInt(copy (*_2)[1 of 4]) -> [74: bb20, otherwise: bb19]; + } + + bb19: { + goto -> bb1; + } + + bb20: { + switchInt(copy (*_2)[2 of 4]) -> [75: bb22, otherwise: bb21]; + } + + bb21: { + goto -> bb19; + } + + bb22: { + switchInt(copy (*_2)[3 of 4]) -> [76: bb24, otherwise: bb23]; + } + + bb23: { + goto -> bb21; + } + + bb24: { + falseEdge -> [real: bb36, imaginary: bb26]; + } + + bb25: { + goto -> bb23; + } + + bb26: { + switchInt(copy (*_2)[1 of 4]) -> [78: bb28, otherwise: bb27]; + } + + bb27: { + goto -> bb1; + } + + bb28: { + switchInt(copy (*_2)[2 of 4]) -> [79: bb30, otherwise: bb29]; + } + + bb29: { + goto -> bb27; + } + + bb30: { + switchInt(copy (*_2)[3 of 4]) -> [80: bb32, otherwise: bb31]; + } + + bb31: { + goto -> bb29; + } + + bb32: { + falseEdge -> [real: bb35, imaginary: bb1]; + } + + bb33: { + goto -> bb31; + } + + bb34: { + FakeRead(ForMatchedPlace(None), _2); + unreachable; + } + + bb35: { + StorageLive(_6); + _6 = MyEnum::D; + _0 = Result::::Ok(move _6); + StorageDead(_6); + goto -> bb39; + } + + bb36: { + StorageLive(_5); + _5 = MyEnum::C; + _0 = Result::::Ok(move _5); + StorageDead(_5); + goto -> bb39; + } + + bb37: { + StorageLive(_4); + _4 = MyEnum::B; + _0 = Result::::Ok(move _4); + StorageDead(_4); + goto -> bb39; + } + + bb38: { + StorageLive(_3); + _3 = MyEnum::A; + _0 = Result::::Ok(move _3); + StorageDead(_3); + goto -> bb39; + } + + bb39: { + StorageDead(_2); + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.custom_element_array_match.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.custom_element_array_match.built.after.panic-abort.mir new file mode 100644 index 0000000000000..4a4d9fe633ce9 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.custom_element_array_match.built.after.panic-abort.mir @@ -0,0 +1,47 @@ +// MIR for `custom_element_array_match` after built + +fn custom_element_array_match(_1: [Element; 4]) -> bool { + debug x => _1; + let mut _0: bool; + let mut _2: &[Element; 4]; + let mut _3: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + _2 = &_1; + _3 = <[Element; 4] as PartialEq>::eq(copy _2, const &[Element(1), Element(2), Element(3), Element(4)]) -> [return: bb4, unwind unreachable]; + } + + bb1: { + _0 = const false; + goto -> bb7; + } + + bb2: { + falseEdge -> [real: bb6, imaginary: bb1]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(move _3) -> [0: bb1, otherwise: bb2]; + } + + bb5: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb6: { + _0 = const true; + goto -> bb7; + } + + bb7: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.custom_element_array_match.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.custom_element_array_match.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..4a4d9fe633ce9 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.custom_element_array_match.built.after.panic-unwind.mir @@ -0,0 +1,47 @@ +// MIR for `custom_element_array_match` after built + +fn custom_element_array_match(_1: [Element; 4]) -> bool { + debug x => _1; + let mut _0: bool; + let mut _2: &[Element; 4]; + let mut _3: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + _2 = &_1; + _3 = <[Element; 4] as PartialEq>::eq(copy _2, const &[Element(1), Element(2), Element(3), Element(4)]) -> [return: bb4, unwind unreachable]; + } + + bb1: { + _0 = const false; + goto -> bb7; + } + + bb2: { + falseEdge -> [real: bb6, imaginary: bb1]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(move _3) -> [0: bb1, otherwise: bb2]; + } + + bb5: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb6: { + _0 = const true; + goto -> bb7; + } + + bb7: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.handwritten_array_match.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.handwritten_array_match.built.after.panic-abort.mir new file mode 100644 index 0000000000000..7f2e610b412a2 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.handwritten_array_match.built.after.panic-abort.mir @@ -0,0 +1,64 @@ +// MIR for `handwritten_array_match` after built + +fn handwritten_array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + switchInt(copy _1[0 of 4]) -> [1: bb2, otherwise: bb1]; + } + + bb1: { + _0 = const false; + goto -> bb12; + } + + bb2: { + switchInt(copy _1[1 of 4]) -> [2: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy _1[2 of 4]) -> [3: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy _1[3 of 4]) -> [4: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb11: { + _0 = const true; + goto -> bb12; + } + + bb12: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.handwritten_array_match.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.handwritten_array_match.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..7f2e610b412a2 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.handwritten_array_match.built.after.panic-unwind.mir @@ -0,0 +1,64 @@ +// MIR for `handwritten_array_match` after built + +fn handwritten_array_match(_1: [u8; 4]) -> bool { + debug x => _1; + let mut _0: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + switchInt(copy _1[0 of 4]) -> [1: bb2, otherwise: bb1]; + } + + bb1: { + _0 = const false; + goto -> bb12; + } + + bb2: { + switchInt(copy _1[1 of 4]) -> [2: bb4, otherwise: bb3]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + switchInt(copy _1[2 of 4]) -> [3: bb6, otherwise: bb5]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy _1[3 of 4]) -> [4: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb9: { + goto -> bb7; + } + + bb10: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb11: { + _0 = const true; + goto -> bb12; + } + + bb12: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.rs b/tests/mir-opt/building/match/aggregate_array_eq.rs new file mode 100644 index 0000000000000..f67456868cea0 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -0,0 +1,105 @@ +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY +//@ compile-flags: -Zmir-opt-level=0 + +// Verify that matching against an array/slice pattern that was expanded from +// a constant (a named constant or a byte-string literal) produces a single +// `PartialEq::eq` call rather than element-by-element comparisons. The call +// must be marked as non-unwinding. +// +// Hand-written array patterns must keep the element-by-element comparisons: +// they only borrow the scrutinee for as long as `PartialEq::eq` would, but +// user intent is respected before the MIR boundary. +// +// In const contexts, the aggregate comparison must NOT be used because +// `PartialEq` is not const-stable. + +#![crate_type = "lib"] + +// EMIT_MIR aggregate_array_eq.array_match.built.after.mir +pub fn array_match(x: [u8; 4]) -> bool { + // CHECK-LABEL: fn array_match( + // CHECK: <[u8; 4] as PartialEq>::eq + // CHECK-SAME: unwind unreachable + // CHECK-NOT: switchInt(copy _1[ + const EXPECTED: [u8; 4] = [1, 2, 3, 4]; + matches!(x, EXPECTED) +} + +// EMIT_MIR aggregate_array_eq.handwritten_array_match.built.after.mir +pub fn handwritten_array_match(x: [u8; 4]) -> bool { + // CHECK-LABEL: fn handwritten_array_match( + // CHECK-NOT: PartialEq + // CHECK: switchInt + matches!(x, [1, 2, 3, 4]) +} + +#[derive(PartialEq, Eq)] +pub struct Element(u8); + +// The element type does not have to be a primitive: the aggregate comparison +// calls `<[Element; 4] as PartialEq>::eq`, which in turn calls the derived +// `PartialEq` implementation for `Element`. +// EMIT_MIR aggregate_array_eq.custom_element_array_match.built.after.mir +pub fn custom_element_array_match(x: [Element; 4]) -> bool { + // CHECK-LABEL: fn custom_element_array_match( + // CHECK: <[Element; 4] as PartialEq>::eq + // CHECK-SAME: unwind unreachable + // CHECK-NOT: switchInt(copy _1[ + const EXPECTED: [Element; 4] = [Element(1), Element(2), Element(3), Element(4)]; + matches!(x, EXPECTED) +} + +// EMIT_MIR aggregate_array_eq.slice_match.built.after.mir +pub fn slice_match(x: &[u8]) -> bool { + // CHECK-LABEL: fn slice_match( + // CHECK: <[u8] as PartialEq>::eq + // CHECK-SAME: unwind unreachable + matches!(x, b"ABCD") +} + +pub enum MyEnum { + A, + B, + C, + D, +} + +// Regression test for https://github.com/rust-lang/rust/issues/103073. +// EMIT_MIR aggregate_array_eq.try_from_matched.built.after.mir +pub fn try_from_matched(value: [u8; 4]) -> Result { + // CHECK-LABEL: fn try_from_matched( + // CHECK: <[u8; 4] as PartialEq>::eq + // CHECK-NOT: switchInt(copy (*_2)[ + match &value { + b"ABCD" => Ok(MyEnum::A), + b"EFGH" => Ok(MyEnum::B), + b"IJKL" => Ok(MyEnum::C), + b"MNOP" => Ok(MyEnum::D), + _ => Err(()), + } +} + +// In a const fn, the aggregate comparison must not be used because +// `PartialEq::eq` cannot be called during const evaluation. +// EMIT_MIR aggregate_array_eq.const_array_match.built.after.mir +pub const fn const_array_match(x: [u8; 4]) -> bool { + // CHECK-LABEL: fn const_array_match( + // CHECK-NOT: PartialEq + // CHECK: switchInt + const EXPECTED: [u8; 4] = [1, 2, 3, 4]; + matches!(x, EXPECTED) +} + +// EMIT_MIR aggregate_array_eq.const_try_from_matched.built.after.mir +pub const fn const_try_from_matched(value: [u8; 4]) -> Result { + // CHECK-LABEL: fn const_try_from_matched( + // CHECK-NOT: PartialEq + // CHECK: switchInt + match &value { + b"ABCD" => Ok(MyEnum::A), + b"EFGH" => Ok(MyEnum::B), + b"IJKL" => Ok(MyEnum::C), + b"MNOP" => Ok(MyEnum::D), + _ => Err(()), + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-abort.mir new file mode 100644 index 0000000000000..fbae5ccbc2e87 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-abort.mir @@ -0,0 +1,63 @@ +// MIR for `slice_match` after built + +fn slice_match(_1: &[u8]) -> bool { + debug x => _1; + let mut _0: bool; + let mut _2: &[u8]; + let mut _3: bool; + let mut _4: usize; + let mut _5: usize; + let mut _6: usize; + let mut _7: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + _5 = PtrMetadata(copy _1); + _4 = move _5; + _6 = const 4_usize; + _7 = Eq(move _4, move _6); + switchInt(move _7) -> [0: bb1, otherwise: bb2]; + } + + bb1: { + _0 = const false; + goto -> bb9; + } + + bb2: { + _2 = &(*_1); + _3 = <[u8] as PartialEq>::eq(copy _2, const b"ABCD") -> [return: bb6, unwind unreachable]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + falseEdge -> [real: bb8, imaginary: bb1]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(move _3) -> [0: bb3, otherwise: bb4]; + } + + bb7: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb8: { + _0 = const true; + goto -> bb9; + } + + bb9: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..fbae5ccbc2e87 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-unwind.mir @@ -0,0 +1,63 @@ +// MIR for `slice_match` after built + +fn slice_match(_1: &[u8]) -> bool { + debug x => _1; + let mut _0: bool; + let mut _2: &[u8]; + let mut _3: bool; + let mut _4: usize; + let mut _5: usize; + let mut _6: usize; + let mut _7: bool; + scope 1 { + } + + bb0: { + PlaceMention(_1); + _5 = PtrMetadata(copy _1); + _4 = move _5; + _6 = const 4_usize; + _7 = Eq(move _4, move _6); + switchInt(move _7) -> [0: bb1, otherwise: bb2]; + } + + bb1: { + _0 = const false; + goto -> bb9; + } + + bb2: { + _2 = &(*_1); + _3 = <[u8] as PartialEq>::eq(copy _2, const b"ABCD") -> [return: bb6, unwind unreachable]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + falseEdge -> [real: bb8, imaginary: bb1]; + } + + bb5: { + goto -> bb3; + } + + bb6: { + switchInt(move _3) -> [0: bb3, otherwise: bb4]; + } + + bb7: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb8: { + _0 = const true; + goto -> bb9; + } + + bb9: { + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-abort.mir new file mode 100644 index 0000000000000..660dab2044a65 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-abort.mir @@ -0,0 +1,153 @@ +// MIR for `try_from_matched` after built + +fn try_from_matched(_1: [u8; 4]) -> Result { + debug value => _1; + let mut _0: std::result::Result; + let mut _2: &[u8; 4]; + let mut _3: &[u8; 4]; + let mut _4: bool; + let mut _5: &[u8; 4]; + let mut _6: bool; + let mut _7: &[u8; 4]; + let mut _8: bool; + let mut _9: &[u8; 4]; + let mut _10: bool; + let mut _11: MyEnum; + let mut _12: MyEnum; + let mut _13: MyEnum; + let mut _14: MyEnum; + let mut _15: (); + + bb0: { + StorageLive(_2); + _2 = &_1; + PlaceMention(_2); + _9 = &(*_2); + _10 = <[u8; 4] as PartialEq>::eq(copy _9, const &*b"ABCD") -> [return: bb19, unwind unreachable]; + } + + bb1: { + StorageLive(_15); + _15 = (); + _0 = Result::::Err(move _15); + StorageDead(_15); + goto -> bb25; + } + + bb2: { + falseEdge -> [real: bb24, imaginary: bb4]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + _7 = &(*_2); + _8 = <[u8; 4] as PartialEq>::eq(copy _7, const &*b"EFGH") -> [return: bb18, unwind unreachable]; + } + + bb5: { + goto -> bb1; + } + + bb6: { + falseEdge -> [real: bb23, imaginary: bb8]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + _5 = &(*_2); + _6 = <[u8; 4] as PartialEq>::eq(copy _5, const &*b"IJKL") -> [return: bb17, unwind unreachable]; + } + + bb9: { + goto -> bb5; + } + + bb10: { + falseEdge -> [real: bb22, imaginary: bb12]; + } + + bb11: { + goto -> bb9; + } + + bb12: { + _3 = &(*_2); + _4 = <[u8; 4] as PartialEq>::eq(copy _3, const &*b"MNOP") -> [return: bb16, unwind unreachable]; + } + + bb13: { + goto -> bb9; + } + + bb14: { + falseEdge -> [real: bb21, imaginary: bb1]; + } + + bb15: { + goto -> bb13; + } + + bb16: { + switchInt(move _4) -> [0: bb13, otherwise: bb14]; + } + + bb17: { + switchInt(move _6) -> [0: bb12, otherwise: bb10]; + } + + bb18: { + switchInt(move _8) -> [0: bb8, otherwise: bb6]; + } + + bb19: { + switchInt(move _10) -> [0: bb4, otherwise: bb2]; + } + + bb20: { + FakeRead(ForMatchedPlace(None), _2); + unreachable; + } + + bb21: { + StorageLive(_14); + _14 = MyEnum::D; + _0 = Result::::Ok(move _14); + StorageDead(_14); + goto -> bb25; + } + + bb22: { + StorageLive(_13); + _13 = MyEnum::C; + _0 = Result::::Ok(move _13); + StorageDead(_13); + goto -> bb25; + } + + bb23: { + StorageLive(_12); + _12 = MyEnum::B; + _0 = Result::::Ok(move _12); + StorageDead(_12); + goto -> bb25; + } + + bb24: { + StorageLive(_11); + _11 = MyEnum::A; + _0 = Result::::Ok(move _11); + StorageDead(_11); + goto -> bb25; + } + + bb25: { + StorageDead(_2); + return; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..660dab2044a65 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-unwind.mir @@ -0,0 +1,153 @@ +// MIR for `try_from_matched` after built + +fn try_from_matched(_1: [u8; 4]) -> Result { + debug value => _1; + let mut _0: std::result::Result; + let mut _2: &[u8; 4]; + let mut _3: &[u8; 4]; + let mut _4: bool; + let mut _5: &[u8; 4]; + let mut _6: bool; + let mut _7: &[u8; 4]; + let mut _8: bool; + let mut _9: &[u8; 4]; + let mut _10: bool; + let mut _11: MyEnum; + let mut _12: MyEnum; + let mut _13: MyEnum; + let mut _14: MyEnum; + let mut _15: (); + + bb0: { + StorageLive(_2); + _2 = &_1; + PlaceMention(_2); + _9 = &(*_2); + _10 = <[u8; 4] as PartialEq>::eq(copy _9, const &*b"ABCD") -> [return: bb19, unwind unreachable]; + } + + bb1: { + StorageLive(_15); + _15 = (); + _0 = Result::::Err(move _15); + StorageDead(_15); + goto -> bb25; + } + + bb2: { + falseEdge -> [real: bb24, imaginary: bb4]; + } + + bb3: { + goto -> bb1; + } + + bb4: { + _7 = &(*_2); + _8 = <[u8; 4] as PartialEq>::eq(copy _7, const &*b"EFGH") -> [return: bb18, unwind unreachable]; + } + + bb5: { + goto -> bb1; + } + + bb6: { + falseEdge -> [real: bb23, imaginary: bb8]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + _5 = &(*_2); + _6 = <[u8; 4] as PartialEq>::eq(copy _5, const &*b"IJKL") -> [return: bb17, unwind unreachable]; + } + + bb9: { + goto -> bb5; + } + + bb10: { + falseEdge -> [real: bb22, imaginary: bb12]; + } + + bb11: { + goto -> bb9; + } + + bb12: { + _3 = &(*_2); + _4 = <[u8; 4] as PartialEq>::eq(copy _3, const &*b"MNOP") -> [return: bb16, unwind unreachable]; + } + + bb13: { + goto -> bb9; + } + + bb14: { + falseEdge -> [real: bb21, imaginary: bb1]; + } + + bb15: { + goto -> bb13; + } + + bb16: { + switchInt(move _4) -> [0: bb13, otherwise: bb14]; + } + + bb17: { + switchInt(move _6) -> [0: bb12, otherwise: bb10]; + } + + bb18: { + switchInt(move _8) -> [0: bb8, otherwise: bb6]; + } + + bb19: { + switchInt(move _10) -> [0: bb4, otherwise: bb2]; + } + + bb20: { + FakeRead(ForMatchedPlace(None), _2); + unreachable; + } + + bb21: { + StorageLive(_14); + _14 = MyEnum::D; + _0 = Result::::Ok(move _14); + StorageDead(_14); + goto -> bb25; + } + + bb22: { + StorageLive(_13); + _13 = MyEnum::C; + _0 = Result::::Ok(move _13); + StorageDead(_13); + goto -> bb25; + } + + bb23: { + StorageLive(_12); + _12 = MyEnum::B; + _0 = Result::::Ok(move _12); + StorageDead(_12); + goto -> bb25; + } + + bb24: { + StorageLive(_11); + _11 = MyEnum::A; + _0 = Result::::Ok(move _11); + StorageDead(_11); + goto -> bb25; + } + + bb25: { + StorageDead(_2); + return; + } +} diff --git a/tests/ui/match/aggregate-array-eq-guard-drop-order.rs b/tests/ui/match/aggregate-array-eq-guard-drop-order.rs new file mode 100644 index 0000000000000..df7b46360c46e --- /dev/null +++ b/tests/ui/match/aggregate-array-eq-guard-drop-order.rs @@ -0,0 +1,32 @@ +//! The aggregate `PartialEq::eq` comparison emitted for constant array/slice +//! patterns must be marked as non-unwinding. If it could unwind, this program +//! would fail borrow-checking in edition 2021: the unwind path from the guard +//! would require the scrutinee temporary, which borrows `referent`, to be +//! dropped in a different order relative to `referent` than on the ordinary +//! path. An explicit `slice == b"ABCD"` guard, which is an unwinding call, +//! still errors here. +//@ check-pass +//@ edition: 2021 + +struct Referent; +impl Drop for Referent { + fn drop(&mut self) {} +} + +struct DropMeFirst<'a>(&'a Referent); +impl Drop for DropMeFirst<'_> { + fn drop(&mut self) {} +} + +fn foo(slice: &[u8]) -> u32 { + let referent = Referent; + match DropMeFirst(&referent) { + _dropped_first if matches!(slice, b"ABCD") => 0, + _dropped_first => 1, + } +} + +fn main() { + assert_eq!(foo(b"ABCD"), 0); + assert_eq!(foo(b"ZZZZ"), 1); +} diff --git a/tests/ui/match/aggregate-array-eq.rs b/tests/ui/match/aggregate-array-eq.rs new file mode 100644 index 0000000000000..95a01d401ef6b --- /dev/null +++ b/tests/ui/match/aggregate-array-eq.rs @@ -0,0 +1,117 @@ +//! Verify that matching against array/slice patterns expanded from constants +//! produces correct results at runtime, complementing the MIR test in +//! `tests/mir-opt/building/match/aggregate_array_eq.rs` which checks that +//! a single aggregate `PartialEq::eq` call is emitted. +//! +//! Also verify that the variants which fall back to element-by-element +//! comparison (hand-written patterns and const contexts) produce the same +//! results. +//@ run-pass + +const EXPECTED: [u8; 4] = [1, 2, 3, 4]; + +fn array_match(x: [u8; 4]) -> bool { + matches!(x, EXPECTED) +} + +fn handwritten_array_match(x: [u8; 4]) -> bool { + matches!(x, [1, 2, 3, 4]) +} + +fn slice_match(x: &[u8]) -> bool { + matches!(x, b"ABCD") +} + +const NESTED: [[u8; 4]; 4] = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]; + +fn nested_array_match(x: [[u8; 4]; 4]) -> bool { + matches!(x, NESTED) +} + +#[derive(Debug, PartialEq)] +enum MyEnum { + A, + B, + C, + D, +} + +// Regression test for https://github.com/rust-lang/rust/issues/103073. +fn try_from_matched(value: [u8; 4]) -> Result { + match &value { + b"ABCD" => Ok(MyEnum::A), + b"EFGH" => Ok(MyEnum::B), + b"IJKL" => Ok(MyEnum::C), + b"MNOP" => Ok(MyEnum::D), + _ => Err(()), + } +} + +// Const fn variants use element-by-element comparison because +// `PartialEq::eq` is not available in const contexts. +const fn const_array_match(x: [u8; 4]) -> bool { + matches!(x, EXPECTED) +} + +const fn const_try_from_matched(value: [u8; 4]) -> Result { + match &value { + b"ABCD" => Ok(MyEnum::A), + b"EFGH" => Ok(MyEnum::B), + b"IJKL" => Ok(MyEnum::C), + b"MNOP" => Ok(MyEnum::D), + _ => Err(()), + } +} + +fn main() { + assert!(array_match([1, 2, 3, 4])); + assert!(!array_match([1, 2, 3, 5])); + assert!(!array_match([0, 0, 0, 0])); + assert!(!array_match([4, 3, 2, 1])); + + assert!(handwritten_array_match([1, 2, 3, 4])); + assert!(!handwritten_array_match([1, 2, 3, 5])); + + assert!(slice_match(b"ABCD")); + assert!(!slice_match(b"ABCE")); + assert!(!slice_match(b"ABC")); + assert!(!slice_match(b"ABCDE")); + assert!(!slice_match(b"")); + + assert!(nested_array_match(NESTED)); + assert!(!nested_array_match([[0; 4]; 4])); + assert!(!nested_array_match([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 0]])); + + assert_eq!(try_from_matched(*b"ABCD"), Ok(MyEnum::A)); + assert_eq!(try_from_matched(*b"EFGH"), Ok(MyEnum::B)); + assert_eq!(try_from_matched(*b"IJKL"), Ok(MyEnum::C)); + assert_eq!(try_from_matched(*b"MNOP"), Ok(MyEnum::D)); + assert_eq!(try_from_matched(*b"ZZZZ"), Err(())); + assert_eq!(try_from_matched(*b"ABCE"), Err(())); + + // Const fn variants called at runtime. + assert!(const_array_match([1, 2, 3, 4])); + assert!(!const_array_match([1, 2, 3, 5])); + assert!(!const_array_match([0, 0, 0, 0])); + assert!(!const_array_match([4, 3, 2, 1])); + + assert_eq!(const_try_from_matched(*b"ABCD"), Ok(MyEnum::A)); + assert_eq!(const_try_from_matched(*b"EFGH"), Ok(MyEnum::B)); + assert_eq!(const_try_from_matched(*b"IJKL"), Ok(MyEnum::C)); + assert_eq!(const_try_from_matched(*b"MNOP"), Ok(MyEnum::D)); + assert_eq!(const_try_from_matched(*b"ZZZZ"), Err(())); + assert_eq!(const_try_from_matched(*b"ABCE"), Err(())); + + // Const fn variants evaluated at compile time. + const MATCH_TRUE: bool = const_array_match([1, 2, 3, 4]); + const MATCH_FALSE: bool = const_array_match([1, 2, 3, 5]); + assert!(MATCH_TRUE); + assert!(!MATCH_FALSE); + + const FROM_ABCD: Result = const_try_from_matched(*b"ABCD"); + const FROM_MNOP: Result = const_try_from_matched(*b"MNOP"); + const FROM_ZZZZ: Result = const_try_from_matched(*b"ZZZZ"); + assert_eq!(FROM_ABCD, Ok(MyEnum::A)); + assert_eq!(FROM_MNOP, Ok(MyEnum::D)); + assert_eq!(FROM_ZZZZ, Err(())); +} diff --git a/tests/ui/thir-print/str-patterns.stdout b/tests/ui/thir-print/str-patterns.stdout index da1f86b8fc591..93f92e2ca0be9 100644 --- a/tests/ui/thir-print/str-patterns.stdout +++ b/tests/ui/thir-print/str-patterns.stdout @@ -48,6 +48,7 @@ Thir { expanded_const: Some( DefId(0:4 ~ str_patterns[fc71]::CONSTANT), ), + expanded_const_value: None, ascriptions: [], }, ), diff --git a/tests/ui/thir-print/thir-tree-array-index.stdout b/tests/ui/thir-print/thir-tree-array-index.stdout index 4e40bcbbf4e07..b167169ca87fa 100644 --- a/tests/ui/thir-print/thir-tree-array-index.stdout +++ b/tests/ui/thir-print/thir-tree-array-index.stdout @@ -129,6 +129,7 @@ body: span: $DIR/thir-tree-array-index.rs:7:7: 7:9 (#0) extra: PatExtra { expanded_const: None + expanded_const_value: None ascriptions: [ Ascription { annotation: CanonicalUserTypeAnnotation { user_ty: Canonical { value: UserType { kind: Ty([usize; 5_usize]), bounds: [] }, max_universe: U0, var_kinds: [] }, span: $DIR/thir-tree-array-index.rs:7:11: 7:21 (#0), inferred_ty: [usize; 5_usize] }, variance: + } ] @@ -279,6 +280,7 @@ body: span: $DIR/thir-tree-array-index.rs:8:7: 8:9 (#0) extra: PatExtra { expanded_const: None + expanded_const_value: None ascriptions: [ Ascription { annotation: CanonicalUserTypeAnnotation { user_ty: Canonical { value: UserType { kind: Ty([usize; 5_usize]), bounds: [] }, max_universe: U0, var_kinds: [] }, span: $DIR/thir-tree-array-index.rs:8:11: 8:21 (#0), inferred_ty: [usize; 5_usize] }, variance: + } ] diff --git a/tests/ui/thir-print/thir-tree-match-for.stdout b/tests/ui/thir-print/thir-tree-match-for.stdout index ac71f6a1d1f07..9f0b5918255e0 100644 --- a/tests/ui/thir-print/thir-tree-match-for.stdout +++ b/tests/ui/thir-print/thir-tree-match-for.stdout @@ -171,6 +171,7 @@ body: span: $DIR/thir-tree-match-for.rs:10:5: 10:9 (#0) extra: PatExtra { expanded_const: None + expanded_const_value: None ascriptions: [] } kind: PatKind {