From bf5922ee25d794de920aa4a1a437cb4a74f863af Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Fri, 3 Apr 2026 19:08:56 +0100 Subject: [PATCH 01/16] Use an aggregate equality comparison for constant array/slice patterns when possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When every element in an array or slice pattern is a constant and there is no `..` subpattern, the match builder now emits a single call to `PartialEq::eq` instead of comparing each element one by one. This drastically reduces the number of MIR basic blocks for large constant-array matches – e.g. a 64-element `[u8; 64]` match previously generated 64 separate comparison blocks and now generates just one `PartialEq::eq` call that LLVM can lower to a `memcmp()` The optimisation is gated on having at least two constant elements. Single-element arrays still use a plain scalar comparison. Example: ```rust const FOO: [u8; 64] = *b"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; pub fn foo(x: &[u8; 64]) -> bool { // Before: 64 basic blocks, one per byte. // After: a single `PartialEq::eq()` call. matches!(x, &FOO) } ``` --- .../src/builder/matches/buckets.rs | 5 + .../src/builder/matches/match_pair.rs | 123 ++++++++++++++---- .../src/builder/matches/mod.rs | 8 ++ .../src/builder/matches/test.rs | 55 +++++++- 4 files changed, 161 insertions(+), 30 deletions(-) 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..5fffa7fa5ffc5 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -4,7 +4,7 @@ use rustc_abi::FieldIdx; use rustc_middle::mir::{Pinnedness, Place, PlaceElem, ProjectionElem}; use rustc_middle::span_bug; use rustc_middle::thir::{Ascription, DerefPatBorrowMode, FieldPat, Pat, PatKind}; -use rustc_middle::ty::{self, Ty, TypeVisitableExt}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::Span; use crate::builder::Builder; @@ -13,6 +13,33 @@ use crate::builder::matches::{ FlatPat, MatchPairTree, PatConstKind, PatternExtraData, SliceLenOp, TestableCase, }; +/// Checks whether every pattern in `elements` is a `PatKind::Constant` and, +/// if so, reconstructs a single aggregate `ty::Value` that represents the whole +/// array or slice. Returns `None` when any element is not a constant or the +/// sequence is too short to benefit from an aggregate comparison. +fn try_reconstruct_aggregate_constant<'tcx>( + tcx: TyCtxt<'tcx>, + aggregate_ty: Ty<'tcx>, + elements: &[Pat<'tcx>], +) -> Option> { + // A single element (or empty array) is not worth an aggregate comparison. + if elements.len() <= 1 { + return None; + } + let branches = elements + .iter() + .map(|pat| { + if let PatKind::Constant { value } = pat.kind { + Some(ty::Const::new_value(tcx, value.valtree, value.ty)) + } else { + None + } + }) + .collect::>>()?; + let valtree = ty::ValTree::from_branches(tcx, branches); + Some(ty::Value { ty: aggregate_ty, valtree }) +} + /// 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 +371,29 @@ 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) + // When all elements are constants and there is no `..` + // subpattern, compare the whole array at once via + // `PartialEq::eq` rather than element by element. + if slice.is_none() + && suffix.is_empty() + && let Some(aggregate_value) = + try_reconstruct_aggregate_constant(cx.tcx, pattern.ty, prefix) { - subpats.push(InterPat::lower_thir_pat(cx, subplace, subpat)); + 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 +405,60 @@ 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) + // When there is no `..`, all elements are constants, and + // there are at least two of them, collapse the individual + // element subpairs into a single aggregate comparison that + // is performed after the length check. + if slice.is_none() + && suffix.is_empty() + && let Some(aggregate_value) = + try_reconstruct_aggregate_constant(cx.tcx, pattern.ty, prefix) { - 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. + 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 ddeb9e084b21d..05b976979c8b8 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 where every element is a constant. + /// 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 6c499315143c3..e7b583db0d997 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -41,6 +41,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 } } @@ -169,16 +172,54 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // 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( + self.non_scalar_compare( block, success_block, fail_block, source_info, + tcx.types.str_, expected_value_operand, Operand::Copy(actual_value_ref_place), ); } + TestKind::AggregateEq { value } => { + let tcx = self.tcx; + let success_block = target_block(TestBranch::Success); + let fail_block = target_block(TestBranch::Failure); + + let aggregate_ty = value.ty; + let ref_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, aggregate_ty); + + // The constant has type `[T; N]` (or `[T]`), but calling + // `PartialEq::eq` requires `&[T; N]` (or `&[T]`) operands. + // Valtree representations are the same with or without the + // reference wrapper, so we can reinterpret by replacing the type. + let expected_value = ty::Value { ty: ref_ty, valtree: value.valtree }; + let expected_operand = + self.literal_operand(test.span, Const::from_ty_value(tcx, expected_value)); + + // Create a reference to the scrutinee place. + let actual_ref_place = self.temp(ref_ty, test.span); + self.cfg.push_assign( + block, + self.source_info(test.span), + actual_ref_place, + Rvalue::Ref(tcx.lifetimes.re_erased, BorrowKind::Shared, place), + ); + + // Compare using `::eq` where `T` is the array or slice type. + self.non_scalar_compare( + block, + success_block, + fail_block, + source_info, + aggregate_ty, + expected_operand, + Operand::Copy(actual_ref_place), + ); + } + TestKind::ScalarEq { value } => { let tcx = self.tcx; let success_block = target_block(TestBranch::Success); @@ -410,19 +451,23 @@ 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. + 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>, ) { - 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); From af46a479ae35ed7efc51b9204e3ca2f2d7451e59 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Fri, 3 Apr 2026 19:20:33 +0100 Subject: [PATCH 02/16] Add a MIR test ensuring we use an aggregate comparison when matching on a large fixed-length array --- ...eq.array_match.built.after.panic-abort.mir | 51 +++++++++++++++++++ ...q.array_match.built.after.panic-unwind.mir | 51 +++++++++++++++++++ .../building/match/aggregate_array_eq.rs | 15 ++++++ 3 files changed, 117 insertions(+) create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-abort.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.rs 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..85778f3bee8cb --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-abort.mir @@ -0,0 +1,51 @@ +// 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: bb8]; + } + + 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; + } + + bb8 (cleanup): { + resume; + } +} 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..85778f3bee8cb --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.array_match.built.after.panic-unwind.mir @@ -0,0 +1,51 @@ +// 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: bb8]; + } + + 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; + } + + bb8 (cleanup): { + resume; + } +} 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..1c77b432ddb7d --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -0,0 +1,15 @@ +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY +//@ compile-flags: -Zmir-opt-level=0 + +// Verify that matching against a constant array pattern produces a single +// `PartialEq::eq` call rather than element-by-element comparisons. + +#![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-NOT: switchInt(copy _1[ + matches!(x, [1, 2, 3, 4]) +} From 299412f2558e734ec35ce9e9e0349d977405e963 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 12 Apr 2026 22:29:45 +0100 Subject: [PATCH 03/16] Add another test, a run-pass one --- tests/ui/match/aggregate-array-eq.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/ui/match/aggregate-array-eq.rs diff --git a/tests/ui/match/aggregate-array-eq.rs b/tests/ui/match/aggregate-array-eq.rs new file mode 100644 index 0000000000000..6622eef77e103 --- /dev/null +++ b/tests/ui/match/aggregate-array-eq.rs @@ -0,0 +1,16 @@ +//! Verify that matching against a constant array pattern 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. +//@ run-pass + +fn array_match(x: [u8; 4]) -> bool { + matches!(x, [1, 2, 3, 4]) +} + +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])); +} From 37fb739f3c2e301b2c5a95cedb16ff6694b74456 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 12 Apr 2026 22:34:14 +0100 Subject: [PATCH 04/16] Extend both tests with the example from https://github.com/rust-lang/rust/issues/103073 --- .../building/match/aggregate_array_eq.rs | 22 +++ ...y_from_matched.built.after.panic-abort.mir | 157 ++++++++++++++++++ ..._from_matched.built.after.panic-unwind.mir | 157 ++++++++++++++++++ tests/ui/match/aggregate-array-eq.rs | 26 +++ 4 files changed, 362 insertions(+) create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-abort.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-unwind.mir diff --git a/tests/mir-opt/building/match/aggregate_array_eq.rs b/tests/mir-opt/building/match/aggregate_array_eq.rs index 1c77b432ddb7d..860c0bcd17113 100644 --- a/tests/mir-opt/building/match/aggregate_array_eq.rs +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -13,3 +13,25 @@ pub fn array_match(x: [u8; 4]) -> bool { // CHECK-NOT: switchInt(copy _1[ matches!(x, [1, 2, 3, 4]) } + +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(()), + } +} 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..008691c44deef --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-abort.mir @@ -0,0 +1,157 @@ +// 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: bb26]; + } + + 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: bb26]; + } + + 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: bb26]; + } + + 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: bb26]; + } + + 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; + } + + bb26 (cleanup): { + resume; + } +} 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..008691c44deef --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.try_from_matched.built.after.panic-unwind.mir @@ -0,0 +1,157 @@ +// 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: bb26]; + } + + 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: bb26]; + } + + 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: bb26]; + } + + 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: bb26]; + } + + 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; + } + + bb26 (cleanup): { + resume; + } +} diff --git a/tests/ui/match/aggregate-array-eq.rs b/tests/ui/match/aggregate-array-eq.rs index 6622eef77e103..7341d441fd80a 100644 --- a/tests/ui/match/aggregate-array-eq.rs +++ b/tests/ui/match/aggregate-array-eq.rs @@ -8,9 +8,35 @@ fn array_match(x: [u8; 4]) -> bool { matches!(x, [1, 2, 3, 4]) } +#[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(()), + } +} + 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_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(())); } From 9862c6ff32c827b1d76bf68c35bcf242171ea103 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Mon, 13 Apr 2026 00:23:49 +0100 Subject: [PATCH 05/16] Make sure the new aggregate equality comparison is excluded from const contexts This is unless the `const_cmp` feature is enabled, in which case `PartialEq` becomes available in said contexts. --- .../src/builder/matches/match_pair.rs | 19 ++++++++++++++++++- compiler/rustc_span/src/symbol.rs | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) 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 5fffa7fa5ffc5..6606096840d3f 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -5,7 +5,7 @@ use rustc_middle::mir::{Pinnedness, Place, PlaceElem, ProjectionElem}; use rustc_middle::span_bug; use rustc_middle::thir::{Ascription, DerefPatBorrowMode, FieldPat, Pat, PatKind}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; -use rustc_span::Span; +use rustc_span::{Span, sym}; use crate::builder::Builder; use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder}; @@ -40,6 +40,21 @@ fn try_reconstruct_aggregate_constant<'tcx>( Some(ty::Value { ty: aggregate_ty, valtree }) } +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 unless `#![feature(const_cmp, const_trait_impl)]` are enabled, + /// because`PartialEq` is not const-stable. + fn can_use_aggregate_eq(&self) -> bool { + let const_partial_eq_enabled = { + let features = self.tcx.features(); + features.enabled(sym::const_trait_impl) && features.enabled(sym::const_cmp) + }; + 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 || const_partial_eq_enabled + } +} + /// 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>( @@ -376,6 +391,7 @@ impl<'tcx> InterPat<'tcx> { // `PartialEq::eq` rather than element by element. if slice.is_none() && suffix.is_empty() + && cx.can_use_aggregate_eq() && let Some(aggregate_value) = try_reconstruct_aggregate_constant(cx.tcx, pattern.ty, prefix) { @@ -415,6 +431,7 @@ impl<'tcx> InterPat<'tcx> { // is performed after the length check. if slice.is_none() && suffix.is_empty() + && cx.can_use_aggregate_eq() && let Some(aggregate_value) = try_reconstruct_aggregate_constant(cx.tcx, pattern.ty, prefix) { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 1b9ab1e05fa8e..f39441a7dc042 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -664,6 +664,7 @@ symbols! { const_block_items, const_c_variadic, const_closures, + const_cmp, const_compare_raw_pointers, const_constructor, const_continue, From 940ef6d839c6240a7aaf29280f16fbfc567f3797 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Mon, 13 Apr 2026 00:24:56 +0100 Subject: [PATCH 06/16] Update the tests to cover const contexts as well --- ...st_array_match.built.after.panic-abort.mir | 64 ++++++ ...t_array_match.built.after.panic-unwind.mir | 64 ++++++ ...y_from_matched.built.after.panic-abort.mir | 197 ++++++++++++++++++ ..._from_matched.built.after.panic-unwind.mir | 197 ++++++++++++++++++ .../building/match/aggregate_array_eq.rs | 26 +++ ...st_array_match.built.after.panic-abort.mir | 51 +++++ ...t_array_match.built.after.panic-unwind.mir | 51 +++++ ...y_from_matched.built.after.panic-abort.mir | 157 ++++++++++++++ ..._from_matched.built.after.panic-unwind.mir | 157 ++++++++++++++ .../match/aggregate_array_eq_const_cmp.rs | 39 ++++ tests/ui/match/aggregate-array-eq.rs | 45 ++++ 11 files changed, 1048 insertions(+) create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-abort.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.const_array_match.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-abort.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.const_try_from_matched.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-abort.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-abort.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.rs 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.rs b/tests/mir-opt/building/match/aggregate_array_eq.rs index 860c0bcd17113..d72eee8d7f853 100644 --- a/tests/mir-opt/building/match/aggregate_array_eq.rs +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -3,6 +3,8 @@ // Verify that matching against a constant array pattern produces a single // `PartialEq::eq` call rather than element-by-element comparisons. +// In const contexts, the aggregate comparison must NOT be used because +// `PartialEq` is not const-stable (unless `#![feature(const_cmp)]`). #![crate_type = "lib"] @@ -35,3 +37,27 @@ pub fn try_from_matched(value: [u8; 4]) -> Result { _ => 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 + matches!(x, [1, 2, 3, 4]) +} + +// 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_const_cmp.const_array_match.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-abort.mir new file mode 100644 index 0000000000000..a0ea09cad59c4 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-abort.mir @@ -0,0 +1,51 @@ +// MIR for `const_array_match` after built + +fn const_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: bb8]; + } + + 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; + } + + bb8 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..a0ea09cad59c4 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-unwind.mir @@ -0,0 +1,51 @@ +// MIR for `const_array_match` after built + +fn const_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: bb8]; + } + + 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; + } + + bb8 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-abort.mir new file mode 100644 index 0000000000000..0a3783666912d --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-abort.mir @@ -0,0 +1,157 @@ +// 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: &[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: bb26]; + } + + 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: bb26]; + } + + 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: bb26]; + } + + 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: bb26]; + } + + 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; + } + + bb26 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..0a3783666912d --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-unwind.mir @@ -0,0 +1,157 @@ +// 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: &[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: bb26]; + } + + 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: bb26]; + } + + 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: bb26]; + } + + 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: bb26]; + } + + 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; + } + + bb26 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.rs b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.rs new file mode 100644 index 0000000000000..d70ab9b6bbcc1 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.rs @@ -0,0 +1,39 @@ +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY +//@ compile-flags: -Zmir-opt-level=0 + +// Verify that with `#![feature(const_cmp)]` and `#![feature(const_trait_impl)]`, +// const functions also use aggregate `PartialEq::eq` comparisons for constant +// array patterns, matching the behaviour of non-const functions. + +#![crate_type = "lib"] +#![feature(const_cmp)] +#![feature(const_trait_impl)] + +pub enum MyEnum { + A, + B, + C, + D, +} + +// EMIT_MIR aggregate_array_eq_const_cmp.const_array_match.built.after.mir +pub const fn const_array_match(x: [u8; 4]) -> bool { + // CHECK-LABEL: fn const_array_match( + // CHECK: <[u8; 4] as PartialEq>::eq + // CHECK-NOT: switchInt(copy _1[ + matches!(x, [1, 2, 3, 4]) +} + +// EMIT_MIR aggregate_array_eq_const_cmp.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: <[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(()), + } +} diff --git a/tests/ui/match/aggregate-array-eq.rs b/tests/ui/match/aggregate-array-eq.rs index 7341d441fd80a..f5234e08b4f52 100644 --- a/tests/ui/match/aggregate-array-eq.rs +++ b/tests/ui/match/aggregate-array-eq.rs @@ -2,6 +2,9 @@ //! 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 const-context variants (which fall back to +//! element-by-element comparison) produce the same results. //@ run-pass fn array_match(x: [u8; 4]) -> bool { @@ -27,6 +30,22 @@ fn try_from_matched(value: [u8; 4]) -> Result { } } +// 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, [1, 2, 3, 4]) +} + +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])); @@ -39,4 +58,30 @@ fn main() { 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(())); } From 6ec9cc1cc62bc1dfe99c8f909327443250a6838d Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 26 Apr 2026 20:33:16 +0100 Subject: [PATCH 07/16] Raise the aggregate equality comparison threshold so simple arrays don't get captured by this logic --- .../rustc_mir_build/src/builder/matches/match_pair.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 6606096840d3f..f84ec7076a58b 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,11 @@ 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; + /// Checks whether every pattern in `elements` is a `PatKind::Constant` and, /// if so, reconstructs a single aggregate `ty::Value` that represents the whole /// array or slice. Returns `None` when any element is not a constant or the @@ -22,8 +27,8 @@ fn try_reconstruct_aggregate_constant<'tcx>( aggregate_ty: Ty<'tcx>, elements: &[Pat<'tcx>], ) -> Option> { - // A single element (or empty array) is not worth an aggregate comparison. - if elements.len() <= 1 { + // Short arrays are not worth an aggregate comparison. + if elements.len() < AGGREGATE_EQ_MIN_LEN { return None; } let branches = elements From 485d99c235b02e54981568315c22313fa84fe76e Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 26 Apr 2026 20:41:12 +0100 Subject: [PATCH 08/16] Merge the AggregateEq and StringEq test arms into a single case --- .../src/builder/matches/test.rs | 76 ++++++------------- 1 file changed, 22 insertions(+), 54 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index e7b583db0d997..3dab9b5d66087 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -141,27 +141,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:?}"); + 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 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 }; + // 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), @@ -169,57 +174,20 @@ 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!) + // 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!) self.non_scalar_compare( block, success_block, fail_block, source_info, - tcx.types.str_, + inner_ty, expected_value_operand, Operand::Copy(actual_value_ref_place), ); } - TestKind::AggregateEq { value } => { - let tcx = self.tcx; - let success_block = target_block(TestBranch::Success); - let fail_block = target_block(TestBranch::Failure); - - let aggregate_ty = value.ty; - let ref_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, aggregate_ty); - - // The constant has type `[T; N]` (or `[T]`), but calling - // `PartialEq::eq` requires `&[T; N]` (or `&[T]`) operands. - // Valtree representations are the same with or without the - // reference wrapper, so we can reinterpret by replacing the type. - let expected_value = ty::Value { ty: ref_ty, valtree: value.valtree }; - let expected_operand = - self.literal_operand(test.span, Const::from_ty_value(tcx, expected_value)); - - // Create a reference to the scrutinee place. - let actual_ref_place = self.temp(ref_ty, test.span); - self.cfg.push_assign( - block, - self.source_info(test.span), - actual_ref_place, - Rvalue::Ref(tcx.lifetimes.re_erased, BorrowKind::Shared, place), - ); - - // Compare using `::eq` where `T` is the array or slice type. - self.non_scalar_compare( - block, - success_block, - fail_block, - source_info, - aggregate_ty, - expected_operand, - Operand::Copy(actual_ref_place), - ); - } - TestKind::ScalarEq { value } => { let tcx = self.tcx; let success_block = target_block(TestBranch::Success); From f36ae4a1ebb47d751c1233a1431c96fb39ddd31f Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 26 Apr 2026 20:45:39 +0100 Subject: [PATCH 09/16] Add a missing space --- compiler/rustc_mir_build/src/builder/matches/match_pair.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f84ec7076a58b..a6519de97930a 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -48,7 +48,7 @@ fn try_reconstruct_aggregate_constant<'tcx>( 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 unless `#![feature(const_cmp, const_trait_impl)]` are enabled, - /// because`PartialEq` is not const-stable. + /// because `PartialEq` is not const-stable. fn can_use_aggregate_eq(&self) -> bool { let const_partial_eq_enabled = { let features = self.tcx.features(); From c3ad87b265c3da04bc83a1c1e7e4e7dec57ef2a3 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 26 Apr 2026 22:23:08 +0100 Subject: [PATCH 10/16] Remove the special treatment of the `const_cmp` and `const_trait_impl` features --- .../src/builder/matches/match_pair.rs | 11 +- compiler/rustc_span/src/symbol.rs | 1 - .../building/match/aggregate_array_eq.rs | 2 +- ...st_array_match.built.after.panic-abort.mir | 51 ------ ...t_array_match.built.after.panic-unwind.mir | 51 ------ ...y_from_matched.built.after.panic-abort.mir | 157 ------------------ ..._from_matched.built.after.panic-unwind.mir | 157 ------------------ .../match/aggregate_array_eq_const_cmp.rs | 39 ----- 8 files changed, 4 insertions(+), 465 deletions(-) delete mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-abort.mir delete mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-unwind.mir delete mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-abort.mir delete mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-unwind.mir delete mode 100644 tests/mir-opt/building/match/aggregate_array_eq_const_cmp.rs 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 a6519de97930a..abb6cb3f88b71 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -5,7 +5,7 @@ use rustc_middle::mir::{Pinnedness, Place, PlaceElem, ProjectionElem}; use rustc_middle::span_bug; use rustc_middle::thir::{Ascription, DerefPatBorrowMode, FieldPat, Pat, PatKind}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; -use rustc_span::{Span, sym}; +use rustc_span::Span; use crate::builder::Builder; use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder}; @@ -47,16 +47,11 @@ fn try_reconstruct_aggregate_constant<'tcx>( 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 unless `#![feature(const_cmp, const_trait_impl)]` are enabled, - /// because `PartialEq` is not const-stable. + /// This is not possible in const contexts, because `PartialEq` is not const-stable yet. fn can_use_aggregate_eq(&self) -> bool { - let const_partial_eq_enabled = { - let features = self.tcx.features(); - features.enabled(sym::const_trait_impl) && features.enabled(sym::const_cmp) - }; 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 || const_partial_eq_enabled + !in_const_context } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index f39441a7dc042..1b9ab1e05fa8e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -664,7 +664,6 @@ symbols! { const_block_items, const_c_variadic, const_closures, - const_cmp, const_compare_raw_pointers, const_constructor, const_continue, diff --git a/tests/mir-opt/building/match/aggregate_array_eq.rs b/tests/mir-opt/building/match/aggregate_array_eq.rs index d72eee8d7f853..94c857458f9ee 100644 --- a/tests/mir-opt/building/match/aggregate_array_eq.rs +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -4,7 +4,7 @@ // Verify that matching against a constant array pattern produces a single // `PartialEq::eq` call rather than element-by-element comparisons. // In const contexts, the aggregate comparison must NOT be used because -// `PartialEq` is not const-stable (unless `#![feature(const_cmp)]`). +// `PartialEq` is not const-stable. #![crate_type = "lib"] diff --git a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-abort.mir deleted file mode 100644 index a0ea09cad59c4..0000000000000 --- a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-abort.mir +++ /dev/null @@ -1,51 +0,0 @@ -// MIR for `const_array_match` after built - -fn const_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: bb8]; - } - - 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; - } - - bb8 (cleanup): { - resume; - } -} diff --git a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-unwind.mir deleted file mode 100644 index a0ea09cad59c4..0000000000000 --- a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_array_match.built.after.panic-unwind.mir +++ /dev/null @@ -1,51 +0,0 @@ -// MIR for `const_array_match` after built - -fn const_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: bb8]; - } - - 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; - } - - bb8 (cleanup): { - resume; - } -} diff --git a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-abort.mir b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-abort.mir deleted file mode 100644 index 0a3783666912d..0000000000000 --- a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-abort.mir +++ /dev/null @@ -1,157 +0,0 @@ -// 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: &[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: bb26]; - } - - 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: bb26]; - } - - 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: bb26]; - } - - 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: bb26]; - } - - 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; - } - - bb26 (cleanup): { - resume; - } -} diff --git a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-unwind.mir b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-unwind.mir deleted file mode 100644 index 0a3783666912d..0000000000000 --- a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.const_try_from_matched.built.after.panic-unwind.mir +++ /dev/null @@ -1,157 +0,0 @@ -// 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: &[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: bb26]; - } - - 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: bb26]; - } - - 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: bb26]; - } - - 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: bb26]; - } - - 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; - } - - bb26 (cleanup): { - resume; - } -} diff --git a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.rs b/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.rs deleted file mode 100644 index d70ab9b6bbcc1..0000000000000 --- a/tests/mir-opt/building/match/aggregate_array_eq_const_cmp.rs +++ /dev/null @@ -1,39 +0,0 @@ -// EMIT_MIR_FOR_EACH_PANIC_STRATEGY -//@ compile-flags: -Zmir-opt-level=0 - -// Verify that with `#![feature(const_cmp)]` and `#![feature(const_trait_impl)]`, -// const functions also use aggregate `PartialEq::eq` comparisons for constant -// array patterns, matching the behaviour of non-const functions. - -#![crate_type = "lib"] -#![feature(const_cmp)] -#![feature(const_trait_impl)] - -pub enum MyEnum { - A, - B, - C, - D, -} - -// EMIT_MIR aggregate_array_eq_const_cmp.const_array_match.built.after.mir -pub const fn const_array_match(x: [u8; 4]) -> bool { - // CHECK-LABEL: fn const_array_match( - // CHECK: <[u8; 4] as PartialEq>::eq - // CHECK-NOT: switchInt(copy _1[ - matches!(x, [1, 2, 3, 4]) -} - -// EMIT_MIR aggregate_array_eq_const_cmp.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: <[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(()), - } -} From 1caeb1061cea8de8ad06a8f05b0d75e7b6b48bdc Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sat, 11 Jul 2026 20:17:43 +0100 Subject: [PATCH 11/16] Pass the original constant value through THIR instead of reconstructing it Following the review feedback, `const_to_pat()` now records the original constant value on the array and slice pattern nodes it expands, and match lowering reads that value back instead of attempting to reconstruct an aggregate constant from the individual element subpatterns. This has two consequences. First, hand-written array and slice patterns are no longer collapsed into aggregate comparisons; the user's intent to match element by element is respected before the MIR boundary. Only patterns that were expanded from an actual constant (a named constant or a byte-string literal) use the aggregate `PartialEq::eq` comparison, and for those the semantics of matching against a constant and comparing with `PartialEq::eq` coincide. Second, nested constant arrays now benefit from the aggregate comparison as well, since the recorded value covers the whole constant, whereas the reconstruction required every immediate element of the pattern to be a leaf constant. --- compiler/rustc_middle/src/thir.rs | 8 ++ .../src/builder/matches/match_pair.rs | 76 ++++++++----------- .../src/builder/matches/mod.rs | 4 +- .../src/thir/pattern/const_to_pat.rs | 10 ++- compiler/rustc_mir_build/src/thir/print.rs | 7 +- ...en_array_match.built.after.panic-abort.mir | 64 ++++++++++++++++ ...n_array_match.built.after.panic-unwind.mir | 64 ++++++++++++++++ .../building/match/aggregate_array_eq.rs | 27 ++++++- ...eq.slice_match.built.after.panic-abort.mir | 67 ++++++++++++++++ ...q.slice_match.built.after.panic-unwind.mir | 67 ++++++++++++++++ tests/ui/match/aggregate-array-eq.rs | 40 ++++++++-- tests/ui/thir-print/str-patterns.stdout | 1 + .../thir-print/thir-tree-array-index.stdout | 2 + .../ui/thir-print/thir-tree-match-for.stdout | 1 + 14 files changed, 381 insertions(+), 57 deletions(-) create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.handwritten_array_match.built.after.panic-abort.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.handwritten_array_match.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-abort.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-unwind.mir 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/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index abb6cb3f88b71..93e4ba874cea2 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -4,7 +4,7 @@ use rustc_abi::FieldIdx; use rustc_middle::mir::{Pinnedness, Place, PlaceElem, ProjectionElem}; use rustc_middle::span_bug; use rustc_middle::thir::{Ascription, DerefPatBorrowMode, FieldPat, Pat, PatKind}; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{self, Ty, TypeVisitableExt}; use rustc_span::Span; use crate::builder::Builder; @@ -18,33 +18,6 @@ use crate::builder::matches::{ /// unlikely to be more expensive than a `PartialEq::eq` call. const AGGREGATE_EQ_MIN_LEN: usize = 4; -/// Checks whether every pattern in `elements` is a `PatKind::Constant` and, -/// if so, reconstructs a single aggregate `ty::Value` that represents the whole -/// array or slice. Returns `None` when any element is not a constant or the -/// sequence is too short to benefit from an aggregate comparison. -fn try_reconstruct_aggregate_constant<'tcx>( - tcx: TyCtxt<'tcx>, - aggregate_ty: Ty<'tcx>, - elements: &[Pat<'tcx>], -) -> Option> { - // Short arrays are not worth an aggregate comparison. - if elements.len() < AGGREGATE_EQ_MIN_LEN { - return None; - } - let branches = elements - .iter() - .map(|pat| { - if let PatKind::Constant { value } = pat.kind { - Some(ty::Const::new_value(tcx, value.valtree, value.ty)) - } else { - None - } - }) - .collect::>>()?; - let valtree = ty::ValTree::from_branches(tcx, branches); - Some(ty::Value { ty: aggregate_ty, valtree }) -} - 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. @@ -53,6 +26,25 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { || !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 @@ -386,15 +378,11 @@ impl<'tcx> InterPat<'tcx> { _ => None, }; if let Some(array_len) = array_len { - // When all elements are constants and there is no `..` - // subpattern, compare the whole array at once via + // 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 slice.is_none() - && suffix.is_empty() - && cx.can_use_aggregate_eq() - && let Some(aggregate_value) = - try_reconstruct_aggregate_constant(cx.tcx, pattern.ty, prefix) - { + 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, @@ -425,16 +413,12 @@ impl<'tcx> InterPat<'tcx> { } } PatKind::Slice { ref prefix, ref slice, ref suffix } => { - // When there is no `..`, all elements are constants, and - // there are at least two of them, collapse the individual - // element subpairs into a single aggregate comparison that - // is performed after the length check. - if slice.is_none() - && suffix.is_empty() - && cx.can_use_aggregate_eq() - && let Some(aggregate_value) = - try_reconstruct_aggregate_constant(cx.tcx, pattern.ty, prefix) - { + // 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 { diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 05b976979c8b8..1fc5b7c66ef1b 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -1248,8 +1248,8 @@ enum PatConstKind { Float, /// Constant string values, tested via string equality. String, - /// Constant array or slice values where every element is a constant. - /// Tested by calling `PartialEq::eq` on the whole aggregate at once, + /// 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 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 0230840ef2fb8..e731b4ca83a81 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 @@ -474,7 +474,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.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 index 94c857458f9ee..8d1bb05b51cca 100644 --- a/tests/mir-opt/building/match/aggregate_array_eq.rs +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -1,8 +1,14 @@ // EMIT_MIR_FOR_EACH_PANIC_STRATEGY //@ compile-flags: -Zmir-opt-level=0 -// Verify that matching against a constant array pattern produces a single +// 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. +// +// 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. @@ -13,9 +19,25 @@ pub fn array_match(x: [u8; 4]) -> bool { // CHECK-LABEL: fn array_match( // CHECK: <[u8; 4] as PartialEq>::eq // 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]) } +// 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 + matches!(x, b"ABCD") +} + pub enum MyEnum { A, B, @@ -45,7 +67,8 @@ pub const fn const_array_match(x: [u8; 4]) -> bool { // CHECK-LABEL: fn const_array_match( // CHECK-NOT: PartialEq // CHECK: switchInt - matches!(x, [1, 2, 3, 4]) + const EXPECTED: [u8; 4] = [1, 2, 3, 4]; + matches!(x, EXPECTED) } // EMIT_MIR aggregate_array_eq.const_try_from_matched.built.after.mir 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..cc0bb7775b7d2 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-abort.mir @@ -0,0 +1,67 @@ +// 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: bb10]; + } + + 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; + } + + bb10 (cleanup): { + resume; + } +} 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..cc0bb7775b7d2 --- /dev/null +++ b/tests/mir-opt/building/match/aggregate_array_eq.slice_match.built.after.panic-unwind.mir @@ -0,0 +1,67 @@ +// 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: bb10]; + } + + 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; + } + + bb10 (cleanup): { + resume; + } +} diff --git a/tests/ui/match/aggregate-array-eq.rs b/tests/ui/match/aggregate-array-eq.rs index f5234e08b4f52..95a01d401ef6b 100644 --- a/tests/ui/match/aggregate-array-eq.rs +++ b/tests/ui/match/aggregate-array-eq.rs @@ -1,16 +1,33 @@ -//! Verify that matching against a constant array pattern produces correct -//! results at runtime, complementing the MIR test in +//! 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 const-context variants (which fall back to -//! element-by-element comparison) produce the same results. +//! 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, @@ -33,7 +50,7 @@ fn try_from_matched(value: [u8; 4]) -> Result { // 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, [1, 2, 3, 4]) + matches!(x, EXPECTED) } const fn const_try_from_matched(value: [u8; 4]) -> Result { @@ -52,6 +69,19 @@ fn main() { 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)); 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 5e32526fc5633..33b141ae11672 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 { From 149d1db5afe294e235bb60012ef4b73ed7da2082 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sat, 11 Jul 2026 20:46:12 +0100 Subject: [PATCH 12/16] Assert that the aggregate equality comparison cannot unwind Give the `PartialEq::eq` call emitted for constant array/slice patterns `UnwindAction::Unreachable` instead of an unwind edge. The built-in `PartialEq` implementations for arrays and slices can be trusted not to panic, and the unwind edge would not be harmless: since the aggregate comparison replaces a series of `SwitchInt` tests that could never unwind, the extra edge would make borrow-checking stricter about the drop order in unwinding code, turning previously accepted programs into errors. The string equality tests keep their unwind edge, as they have always had one. --- .../src/builder/matches/test.rs | 23 +++++++++++-- ...eq.array_match.built.after.panic-abort.mir | 6 +--- ...q.array_match.built.after.panic-unwind.mir | 6 +--- .../building/match/aggregate_array_eq.rs | 5 ++- ...eq.slice_match.built.after.panic-abort.mir | 6 +--- ...q.slice_match.built.after.panic-unwind.mir | 6 +--- ...y_from_matched.built.after.panic-abort.mir | 12 +++---- ..._from_matched.built.after.panic-unwind.mir | 12 +++---- .../aggregate-array-eq-guard-drop-order.rs | 32 +++++++++++++++++++ 9 files changed, 69 insertions(+), 39 deletions(-) create mode 100644 tests/ui/match/aggregate-array-eq-guard-drop-order.rs diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index 3dab9b5d66087..7f01263278465 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -177,6 +177,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // 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 call the built-in `PartialEq` impls for + // arrays and slices, which can be trusted not to panic, so they are + // asserted not to unwind. An unwind edge here would be a breaking + // change: string equality tests have always had one, but the + // aggregate tests replace a series of `SwitchInt`s that never could + // unwind, and the extra edge would make borrow-checking stricter. + let can_unwind = matches!(test.kind, TestKind::StringEq { .. }); self.non_scalar_compare( block, success_block, @@ -185,6 +193,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { inner_ty, expected_value_operand, Operand::Copy(actual_value_ref_place), + can_unwind, ); } @@ -423,6 +432,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// /// `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, @@ -432,6 +448,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { compared_ty: Ty<'tcx>, expect: Operand<'tcx>, val: Operand<'tcx>, + can_unwind: bool, ) { let eq_def_id = self.tcx.require_lang_item(LangItem::PartialEq, source_info.span); let method = @@ -462,12 +479,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/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 index 85778f3bee8cb..97ca0c11c39a5 100644 --- 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 @@ -11,7 +11,7 @@ fn array_match(_1: [u8; 4]) -> bool { bb0: { PlaceMention(_1); _2 = &_1; - _3 = <[u8; 4] as PartialEq>::eq(copy _2, const &*b"\x01\x02\x03\x04") -> [return: bb4, unwind: bb8]; + _3 = <[u8; 4] as PartialEq>::eq(copy _2, const &*b"\x01\x02\x03\x04") -> [return: bb4, unwind unreachable]; } bb1: { @@ -44,8 +44,4 @@ fn array_match(_1: [u8; 4]) -> bool { bb7: { return; } - - bb8 (cleanup): { - resume; - } } 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 index 85778f3bee8cb..97ca0c11c39a5 100644 --- 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 @@ -11,7 +11,7 @@ fn array_match(_1: [u8; 4]) -> bool { bb0: { PlaceMention(_1); _2 = &_1; - _3 = <[u8; 4] as PartialEq>::eq(copy _2, const &*b"\x01\x02\x03\x04") -> [return: bb4, unwind: bb8]; + _3 = <[u8; 4] as PartialEq>::eq(copy _2, const &*b"\x01\x02\x03\x04") -> [return: bb4, unwind unreachable]; } bb1: { @@ -44,8 +44,4 @@ fn array_match(_1: [u8; 4]) -> bool { bb7: { return; } - - bb8 (cleanup): { - resume; - } } diff --git a/tests/mir-opt/building/match/aggregate_array_eq.rs b/tests/mir-opt/building/match/aggregate_array_eq.rs index 8d1bb05b51cca..0f3b766f8b101 100644 --- a/tests/mir-opt/building/match/aggregate_array_eq.rs +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -3,7 +3,8 @@ // 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. +// `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 @@ -18,6 +19,7 @@ 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) @@ -35,6 +37,7 @@ pub fn handwritten_array_match(x: [u8; 4]) -> bool { 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") } 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 index cc0bb7775b7d2..fbae5ccbc2e87 100644 --- 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 @@ -28,7 +28,7 @@ fn slice_match(_1: &[u8]) -> bool { bb2: { _2 = &(*_1); - _3 = <[u8] as PartialEq>::eq(copy _2, const b"ABCD") -> [return: bb6, unwind: bb10]; + _3 = <[u8] as PartialEq>::eq(copy _2, const b"ABCD") -> [return: bb6, unwind unreachable]; } bb3: { @@ -60,8 +60,4 @@ fn slice_match(_1: &[u8]) -> bool { bb9: { return; } - - bb10 (cleanup): { - resume; - } } 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 index cc0bb7775b7d2..fbae5ccbc2e87 100644 --- 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 @@ -28,7 +28,7 @@ fn slice_match(_1: &[u8]) -> bool { bb2: { _2 = &(*_1); - _3 = <[u8] as PartialEq>::eq(copy _2, const b"ABCD") -> [return: bb6, unwind: bb10]; + _3 = <[u8] as PartialEq>::eq(copy _2, const b"ABCD") -> [return: bb6, unwind unreachable]; } bb3: { @@ -60,8 +60,4 @@ fn slice_match(_1: &[u8]) -> bool { bb9: { return; } - - bb10 (cleanup): { - resume; - } } 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 index 008691c44deef..660dab2044a65 100644 --- 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 @@ -23,7 +23,7 @@ fn try_from_matched(_1: [u8; 4]) -> Result { _2 = &_1; PlaceMention(_2); _9 = &(*_2); - _10 = <[u8; 4] as PartialEq>::eq(copy _9, const &*b"ABCD") -> [return: bb19, unwind: bb26]; + _10 = <[u8; 4] as PartialEq>::eq(copy _9, const &*b"ABCD") -> [return: bb19, unwind unreachable]; } bb1: { @@ -44,7 +44,7 @@ fn try_from_matched(_1: [u8; 4]) -> Result { bb4: { _7 = &(*_2); - _8 = <[u8; 4] as PartialEq>::eq(copy _7, const &*b"EFGH") -> [return: bb18, unwind: bb26]; + _8 = <[u8; 4] as PartialEq>::eq(copy _7, const &*b"EFGH") -> [return: bb18, unwind unreachable]; } bb5: { @@ -61,7 +61,7 @@ fn try_from_matched(_1: [u8; 4]) -> Result { bb8: { _5 = &(*_2); - _6 = <[u8; 4] as PartialEq>::eq(copy _5, const &*b"IJKL") -> [return: bb17, unwind: bb26]; + _6 = <[u8; 4] as PartialEq>::eq(copy _5, const &*b"IJKL") -> [return: bb17, unwind unreachable]; } bb9: { @@ -78,7 +78,7 @@ fn try_from_matched(_1: [u8; 4]) -> Result { bb12: { _3 = &(*_2); - _4 = <[u8; 4] as PartialEq>::eq(copy _3, const &*b"MNOP") -> [return: bb16, unwind: bb26]; + _4 = <[u8; 4] as PartialEq>::eq(copy _3, const &*b"MNOP") -> [return: bb16, unwind unreachable]; } bb13: { @@ -150,8 +150,4 @@ fn try_from_matched(_1: [u8; 4]) -> Result { StorageDead(_2); return; } - - bb26 (cleanup): { - resume; - } } 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 index 008691c44deef..660dab2044a65 100644 --- 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 @@ -23,7 +23,7 @@ fn try_from_matched(_1: [u8; 4]) -> Result { _2 = &_1; PlaceMention(_2); _9 = &(*_2); - _10 = <[u8; 4] as PartialEq>::eq(copy _9, const &*b"ABCD") -> [return: bb19, unwind: bb26]; + _10 = <[u8; 4] as PartialEq>::eq(copy _9, const &*b"ABCD") -> [return: bb19, unwind unreachable]; } bb1: { @@ -44,7 +44,7 @@ fn try_from_matched(_1: [u8; 4]) -> Result { bb4: { _7 = &(*_2); - _8 = <[u8; 4] as PartialEq>::eq(copy _7, const &*b"EFGH") -> [return: bb18, unwind: bb26]; + _8 = <[u8; 4] as PartialEq>::eq(copy _7, const &*b"EFGH") -> [return: bb18, unwind unreachable]; } bb5: { @@ -61,7 +61,7 @@ fn try_from_matched(_1: [u8; 4]) -> Result { bb8: { _5 = &(*_2); - _6 = <[u8; 4] as PartialEq>::eq(copy _5, const &*b"IJKL") -> [return: bb17, unwind: bb26]; + _6 = <[u8; 4] as PartialEq>::eq(copy _5, const &*b"IJKL") -> [return: bb17, unwind unreachable]; } bb9: { @@ -78,7 +78,7 @@ fn try_from_matched(_1: [u8; 4]) -> Result { bb12: { _3 = &(*_2); - _4 = <[u8; 4] as PartialEq>::eq(copy _3, const &*b"MNOP") -> [return: bb16, unwind: bb26]; + _4 = <[u8; 4] as PartialEq>::eq(copy _3, const &*b"MNOP") -> [return: bb16, unwind unreachable]; } bb13: { @@ -150,8 +150,4 @@ fn try_from_matched(_1: [u8; 4]) -> Result { StorageDead(_2); return; } - - bb26 (cleanup): { - resume; - } } 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); +} From 08a103ef45b864b863228e7e0cfdf44ec33a6c0d Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 2 Aug 2026 12:37:58 +0100 Subject: [PATCH 13/16] Add a MIR test for a constant array with a custom element type --- ...nt_array_match.built.after.panic-abort.mir | 47 +++++++++++++++++++ ...t_array_match.built.after.panic-unwind.mir | 47 +++++++++++++++++++ .../building/match/aggregate_array_eq.rs | 16 +++++++ 3 files changed, 110 insertions(+) create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.custom_element_array_match.built.after.panic-abort.mir create mode 100644 tests/mir-opt/building/match/aggregate_array_eq.custom_element_array_match.built.after.panic-unwind.mir 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.rs b/tests/mir-opt/building/match/aggregate_array_eq.rs index 0f3b766f8b101..f67456868cea0 100644 --- a/tests/mir-opt/building/match/aggregate_array_eq.rs +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -33,6 +33,22 @@ pub fn handwritten_array_match(x: [u8; 4]) -> bool { 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( From 6b8eeaa3840a00c346bd230cc4ab15d796268ba0 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 2 Aug 2026 22:47:38 +0100 Subject: [PATCH 14/16] Expand on why the aggregate comparison is assumed not to unwind --- compiler/rustc_mir_build/src/builder/matches/test.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index 7f01263278465..8c49be074098c 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -178,12 +178,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // (Interestingly this means that, for `str`, exhaustiveness analysis // relies for soundness on the `PartialEq` impl for `str` to be correct!) // - // The aggregate comparisons call the built-in `PartialEq` impls for - // arrays and slices, which can be trusted not to panic, so they are - // asserted not to unwind. An unwind edge here would be a breaking - // change: string equality tests have always had one, but the - // aggregate tests replace a series of `SwitchInt`s that never could - // unwind, and the extra edge would make borrow-checking stricter. + // 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, From 06c257998a0b804029e3d61a0636194c692a281e Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 23 Aug 2026 15:12:37 +0100 Subject: [PATCH 15/16] Note in the comment that exhaustiveness relies on `PartialEq` for aggregates too --- compiler/rustc_mir_build/src/builder/matches/test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index 8c49be074098c..0a912862f13b2 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -175,8 +175,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); // 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!) + // (Interestingly this means that exhaustiveness analysis relies, for + // soundness, on that `PartialEq` impl agreeing with structural equality.) // // The aggregate comparisons, unlike the long-standing string ones, are // asserted not to unwind, since an unwind edge would make From d2a16fb1312fa6e7e05f20d8b4ca6232b47b8fde Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 23 Aug 2026 15:41:29 +0100 Subject: [PATCH 16/16] Restrict the aggregate comparison to bytewise-comparable element types --- .../src/builder/matches/match_pair.rs | 18 ++++++++- .../src/builder/matches/test.rs | 6 +-- ...nt_array_match.built.after.panic-abort.mir | 37 ++++++++++++++----- ...t_array_match.built.after.panic-unwind.mir | 37 ++++++++++++++----- .../building/match/aggregate_array_eq.rs | 18 ++++----- 5 files changed, 82 insertions(+), 34 deletions(-) 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 93e4ba874cea2..1ebe1c53116c6 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -18,6 +18,16 @@ use crate::builder::matches::{ /// unlikely to be more expensive than a `PartialEq::eq` call. const AGGREGATE_EQ_MIN_LEN: usize = 4; +/// Whether arrays and slices with this element type may be compared as an aggregate. +/// +/// We rely on `PartialEq::eq` agreeing with structural equality and on it not +/// panicking, so we restrict ourselves to the primitives that +/// `core::cmp::BytewiseEq` is implemented for. For those, the comparison of the +/// whole aggregate is done by the `compare_bytes` and `raw_eq` intrinsics. +fn is_bytewise_comparable(element_ty: Ty<'_>) -> bool { + matches!(element_ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_)) +} + 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. @@ -40,7 +50,13 @@ impl<'a, 'tcx> Builder<'a, '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() { + let (ty::Array(element_ty, _) | ty::Slice(element_ty)) = *pattern.ty.kind() else { + return None; + }; + if element_count < AGGREGATE_EQ_MIN_LEN + || !is_bytewise_comparable(element_ty) + || !self.can_use_aggregate_eq() + { return None; } Some(value) diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index 0a912862f13b2..e5f476a467214 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -181,9 +181,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // 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. + // That is sound because they are only used for element types whose + // `PartialEq` impl compares the aggregates directly with the + // `compare_bytes` and `raw_eq` intrinsics, which cannot panic. let can_unwind = matches!(test.kind, TestKind::StringEq { .. }); self.non_scalar_compare( block, 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 index 4a4d9fe633ce9..252b4858d2b62 100644 --- 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 @@ -3,24 +3,21 @@ 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]; + switchInt(copy (_1[0 of 4].0: u8)) -> [1: bb2, otherwise: bb1]; } bb1: { _0 = const false; - goto -> bb7; + goto -> bb12; } bb2: { - falseEdge -> [real: bb6, imaginary: bb1]; + switchInt(copy (_1[1 of 4].0: u8)) -> [2: bb4, otherwise: bb3]; } bb3: { @@ -28,20 +25,40 @@ fn custom_element_array_match(_1: [Element; 4]) -> bool { } bb4: { - switchInt(move _3) -> [0: bb1, otherwise: bb2]; + switchInt(copy (_1[2 of 4].0: u8)) -> [3: bb6, otherwise: bb5]; } bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy (_1[3 of 4].0: u8)) -> [4: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb9: { + goto -> bb7; + } + + bb10: { FakeRead(ForMatchedPlace(None), _1); unreachable; } - bb6: { + bb11: { _0 = const true; - goto -> bb7; + goto -> bb12; } - bb7: { + bb12: { 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 index 4a4d9fe633ce9..252b4858d2b62 100644 --- 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 @@ -3,24 +3,21 @@ 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]; + switchInt(copy (_1[0 of 4].0: u8)) -> [1: bb2, otherwise: bb1]; } bb1: { _0 = const false; - goto -> bb7; + goto -> bb12; } bb2: { - falseEdge -> [real: bb6, imaginary: bb1]; + switchInt(copy (_1[1 of 4].0: u8)) -> [2: bb4, otherwise: bb3]; } bb3: { @@ -28,20 +25,40 @@ fn custom_element_array_match(_1: [Element; 4]) -> bool { } bb4: { - switchInt(move _3) -> [0: bb1, otherwise: bb2]; + switchInt(copy (_1[2 of 4].0: u8)) -> [3: bb6, otherwise: bb5]; } bb5: { + goto -> bb3; + } + + bb6: { + switchInt(copy (_1[3 of 4].0: u8)) -> [4: bb8, otherwise: bb7]; + } + + bb7: { + goto -> bb5; + } + + bb8: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb9: { + goto -> bb7; + } + + bb10: { FakeRead(ForMatchedPlace(None), _1); unreachable; } - bb6: { + bb11: { _0 = const true; - goto -> bb7; + goto -> bb12; } - bb7: { + bb12: { return; } } diff --git a/tests/mir-opt/building/match/aggregate_array_eq.rs b/tests/mir-opt/building/match/aggregate_array_eq.rs index f67456868cea0..85904f8450994 100644 --- a/tests/mir-opt/building/match/aggregate_array_eq.rs +++ b/tests/mir-opt/building/match/aggregate_array_eq.rs @@ -1,10 +1,10 @@ // 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. +// Verify that matching against an array/slice pattern of a bytewise-comparable +// primitive type 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 @@ -36,15 +36,13 @@ pub fn handwritten_array_match(x: [u8; 4]) -> bool { #[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`. +// The aggregate comparison is limited to bytewise-comparable primitive element +// types, whose `PartialEq` implementation is known not to panic. // 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[ + // CHECK-NOT: PartialEq + // CHECK: switchInt const EXPECTED: [Element; 4] = [Element(1), Element(2), Element(3), Element(4)]; matches!(x, EXPECTED) }