From c6676a2730ad8ad6da44e1a2629e1ba9e0d7923f Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Fri, 3 Apr 2026 19:08:56 +0100 Subject: [PATCH 01/10] 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 | 131 ++++++++++++++---- .../src/builder/matches/mod.rs | 8 ++ .../src/builder/matches/test.rs | 54 +++++++- 4 files changed, 168 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 e2d00238e2d59..84d48f5cb6589 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::*; use rustc_middle::span_bug; use rustc_middle::thir::*; -use rustc_middle::ty::{self, Ty, TypeVisitableExt}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use crate::builder::Builder; use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder}; @@ -12,6 +12,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>( @@ -220,10 +247,35 @@ impl<'tcx> MatchPairTree<'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) { - MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); + 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, + ) { + MatchPairTree::for_pattern( + subplace, + subpat, + cx, + &mut subpairs, + extra_data, + ); + } + None } } else { // If the array length couldn't be determined, ignore the @@ -235,33 +287,62 @@ impl<'tcx> MatchPairTree<'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) { - MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); - } - - if prefix.is_empty() && slice.is_some() && suffix.is_empty() { - // This pattern is shaped like `[..]`. 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 + subpairs.push(MatchPairTree { + place, + testable_case: TestableCase::Constant { + value: aggregate_value, + kind: PatConstKind::Aggregate, }, + subpairs: Vec::new(), + pattern_span: pattern.span, + }); + Some(TestableCase::Slice { + len: u64::try_from(prefix.len()).unwrap(), + op: SliceLenOp::Equal, }) + } else { + for (subplace, subpat) in + prefix_slice_suffix(&place_builder, None, prefix, slice, suffix) + { + MatchPairTree::for_pattern( + subplace, + subpat, + cx, + &mut subpairs, + extra_data, + ); + } + + if prefix.is_empty() && slice.is_some() && suffix.is_empty() { + // This pattern is shaped like `[..]`. 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 5604e86e06722..e92f79709396a 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -1266,6 +1266,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 @@ -1351,6 +1355,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 9b7b6f574fe3f..a6ab7c59aa094 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -40,6 +40,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { TestableCase::Constant { value, kind: PatConstKind::String } => { TestKind::StringEq { value } } + TestableCase::Constant { value, kind: PatConstKind::Aggregate } => { + TestKind::AggregateEq { value } + } TestableCase::Constant { value, kind: PatConstKind::Float | PatConstKind::Other } => { TestKind::ScalarEq { value } } @@ -168,16 +171,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); @@ -404,19 +445,22 @@ 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, str_ty]); + let method = trait_method(self.tcx, eq_def_id, sym::eq, [compared_ty, compared_ty]); let bool_ty = self.tcx.types.bool; let eq_result = self.temp(bool_ty, source_info.span); From 05633c2e53128097050ff28f86e0b6cb1156ed8a Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Fri, 3 Apr 2026 19:20:33 +0100 Subject: [PATCH 02/10] 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 6049501ed8f2b0689c32f914b1973763746f219f Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 12 Apr 2026 22:29:45 +0100 Subject: [PATCH 03/10] 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 ba47e98d46bda6f3aea44e348a8b215c67de15b4 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 12 Apr 2026 22:34:14 +0100 Subject: [PATCH 04/10] 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 00fd1a1e7bde31243f67edf28df2067c50040c03 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Mon, 13 Apr 2026 00:23:49 +0100 Subject: [PATCH 05/10] 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 | 18 ++++++++++++++++++ compiler/rustc_span/src/symbol.rs | 1 + 2 files changed, 19 insertions(+) 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 84d48f5cb6589..363eacaa20fc4 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -5,6 +5,7 @@ use rustc_middle::mir::*; use rustc_middle::span_bug; use rustc_middle::thir::*; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_span::sym; use crate::builder::Builder; use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder}; @@ -39,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>( @@ -252,6 +268,7 @@ impl<'tcx> MatchPairTree<'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) { @@ -297,6 +314,7 @@ impl<'tcx> MatchPairTree<'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 4cacdbd3408a5..2a7a47bbc8764 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -648,6 +648,7 @@ symbols! { const_block_items, const_c_variadic, const_closures, + const_cmp, const_compare_raw_pointers, const_constructor, const_continue, From 85524ad6a10cd3c5532687343e269be9e95e243a Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Mon, 13 Apr 2026 00:24:56 +0100 Subject: [PATCH 06/10] 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 3da8657741bd6b6a9ca7299e59cc70422bf4af92 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 26 Apr 2026 20:33:16 +0100 Subject: [PATCH 07/10] 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 363eacaa20fc4..aa743bac6706d 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 5ba2ad7fccb6fecbb1266565669619c2a9276712 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 26 Apr 2026 20:41:12 +0100 Subject: [PATCH 08/10] 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 a6ab7c59aa094..9a7d668f74b14 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -140,27 +140,32 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.terminate(block, self.source_info(match_start_span), terminator); } - TestKind::StringEq { value } => { + TestKind::StringEq { value } | TestKind::AggregateEq { value } => { let tcx = self.tcx; let success_block = target_block(TestBranch::Success); let fail_block = target_block(TestBranch::Failure); - let ref_str_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, tcx.types.str_); - assert!(ref_str_ty.is_imm_ref_str(), "{ref_str_ty:?}"); + 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), @@ -168,57 +173,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 e55204551cc0874043cc7973a48683d8e55ee781 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 26 Apr 2026 20:45:39 +0100 Subject: [PATCH 09/10] Add a missing space --- .../rustc_mir_build/src/builder/matches/match_pair.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 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 aa743bac6706d..40f0dddb0abeb 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(); @@ -340,13 +340,7 @@ impl<'tcx> MatchPairTree<'tcx> { for (subplace, subpat) in prefix_slice_suffix(&place_builder, None, prefix, slice, suffix) { - MatchPairTree::for_pattern( - subplace, - subpat, - cx, - &mut subpairs, - extra_data, - ); + MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); } if prefix.is_empty() && slice.is_some() && suffix.is_empty() { From a287bc901f43975187c0a850a9dc9d1ef64ec7b5 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sun, 26 Apr 2026 22:23:08 +0100 Subject: [PATCH 10/10] Remove the special treatment of the `const_cmp` and `const_trait_impl` features --- .../src/builder/matches/match_pair.rs | 10 +- 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, 3 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 40f0dddb0abeb..11fe6bdca3cf9 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,6 @@ use rustc_middle::mir::*; use rustc_middle::span_bug; use rustc_middle::thir::*; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; -use rustc_span::sym; use crate::builder::Builder; use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder}; @@ -47,16 +46,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 2a7a47bbc8764..4cacdbd3408a5 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -648,7 +648,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(()), - } -}