Rollup merge of #151103 - array-pat-len, r=Nadrieril,petrochenkov

mir_build: Simplify length-determination and indexing for array/slice patterns

The existing length-determination code in `prefix_slice_suffix` has ended up overly complicated, partly because it doesn't know in advance whether the pattern is supposed to be an array pattern or a slice pattern.

Pulling most of that step out into the `PatKind::Array` arm makes the whole thing a bit nicer overall.

There should (hopefully) be no change to compiler output. The biggest “functional” change is that we now discard the subpatterns of an array pattern of unknowable length, instead of treating it as a slice pattern. I'm not aware of any way for this to make an observable difference, and it can only occur when compilation is already doomed to fail.
This commit is contained in:
Stuart Cook 2026-01-15 21:39:03 +11:00 committed by GitHub
commit 69f0a498ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 67 additions and 51 deletions

View file

@ -1221,25 +1221,32 @@ pub enum ProjectionElem<V, T> {
/// thing is true of the `ConstantIndex` and `Subslice` projections below.
Index(V),
/// These indices are generated by slice patterns. Easiest to explain
/// by example:
/// These endpoint-relative indices are generated by slice/array patterns.
///
/// For array types, `offset` is always relative to the start of the array.
/// For slice types, `from_end` determines whether `offset` is relative to
/// the start or the end of the slice being inspected.
///
/// Slice-pattern indices are easiest to explain by the position of `X` in
/// these examples:
///
/// ```ignore (illustrative)
/// [X, _, .._, _, _] => { offset: 0, min_length: 4, from_end: false },
/// [_, X, .._, _, _] => { offset: 1, min_length: 4, from_end: false },
/// [_, _, .._, X, _] => { offset: 2, min_length: 4, from_end: true },
/// [_, _, .._, _, X] => { offset: 1, min_length: 4, from_end: true },
/// [X, _, .., _, _] => { offset: 0, min_length: 4, from_end: false },
/// [_, X, .., _, _] => { offset: 1, min_length: 4, from_end: false },
/// [_, _, .., X, _] => { offset: 2, min_length: 4, from_end: true },
/// [_, _, .., _, X] => { offset: 1, min_length: 4, from_end: true },
/// ```
ConstantIndex {
/// index or -index (in Python terms), depending on from_end
/// - If `from_end == false`, this is a 0-based offset from the start of the array/slice.
/// - If `from_end == true`, this is a 1-based offset from the end of the slice.
offset: u64,
/// The thing being indexed must be at least this long -- otherwise, the
/// projection is UB.
///
/// For arrays this is always the exact length.
min_length: u64,
/// Counting backwards from end? This is always false when indexing an
/// array.
/// If `true`, `offset` is a 1-based offset from the end of the slice.
/// Always false when indexing an array.
from_end: bool,
},

View file

@ -40,58 +40,44 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
extra_data: &mut PatternExtraData<'tcx>,
place: &PlaceBuilder<'tcx>,
array_len: Option<u64>,
prefix: &[Pat<'tcx>],
opt_slice: &Option<Box<Pat<'tcx>>>,
suffix: &[Pat<'tcx>],
) {
let tcx = self.tcx;
let (min_length, exact_size) = if let Some(place_resolved) = place.try_to_place(self) {
let place_ty = place_resolved.ty(&self.local_decls, tcx).ty;
match place_ty.kind() {
ty::Array(_, length) => {
if let Some(length) = length.try_to_target_usize(tcx) {
(length, true)
} else {
// This can happen when the array length is a generic const
// expression that couldn't be evaluated (e.g., due to an error).
// Since there's already a compilation error, we use a fallback
// to avoid an ICE.
tcx.dcx().span_delayed_bug(
tcx.def_span(self.def_id),
"array length in pattern couldn't be evaluated",
);
((prefix.len() + suffix.len()).try_into().unwrap(), false)
}
}
_ => ((prefix.len() + suffix.len()).try_into().unwrap(), false),
}
} else {
((prefix.len() + suffix.len()).try_into().unwrap(), false)
let prefix_len = u64::try_from(prefix.len()).unwrap();
let suffix_len = u64::try_from(suffix.len()).unwrap();
// For slice patterns with a `..` followed by 0 or more suffix subpatterns,
// the actual slice index of those subpatterns isn't statically known, so
// we have to index them relative to the end of the slice.
//
// For array patterns, all subpatterns are indexed relative to the start.
let (min_length, is_array) = match array_len {
Some(len) => (len, true),
None => (prefix_len + suffix_len, false),
};
for (idx, subpattern) in prefix.iter().enumerate() {
let elem =
ProjectionElem::ConstantIndex { offset: idx as u64, min_length, from_end: false };
for (offset, subpattern) in (0u64..).zip(prefix) {
let elem = ProjectionElem::ConstantIndex { offset, min_length, from_end: false };
let place = place.clone_project(elem);
MatchPairTree::for_pattern(place, subpattern, self, match_pairs, extra_data)
}
if let Some(subslice_pat) = opt_slice {
let suffix_len = suffix.len() as u64;
let subslice = place.clone_project(PlaceElem::Subslice {
from: prefix.len() as u64,
to: if exact_size { min_length - suffix_len } else { suffix_len },
from_end: !exact_size,
from: prefix_len,
to: if is_array { min_length - suffix_len } else { suffix_len },
from_end: !is_array,
});
MatchPairTree::for_pattern(subslice, subslice_pat, self, match_pairs, extra_data);
}
for (idx, subpattern) in suffix.iter().rev().enumerate() {
let end_offset = (idx + 1) as u64;
for (end_offset, subpattern) in (1u64..).zip(suffix.iter().rev()) {
let elem = ProjectionElem::ConstantIndex {
offset: if exact_size { min_length - end_offset } else { end_offset },
offset: if is_array { min_length - end_offset } else { end_offset },
min_length,
from_end: !exact_size,
from_end: !is_array,
};
let place = place.clone_project(elem);
MatchPairTree::for_pattern(place, subpattern, self, match_pairs, extra_data)
@ -256,14 +242,36 @@ impl<'tcx> MatchPairTree<'tcx> {
}
PatKind::Array { ref prefix, ref slice, ref suffix } => {
cx.prefix_slice_suffix(
&mut subpairs,
extra_data,
&place_builder,
prefix,
slice,
suffix,
);
// Determine the statically-known length of the array type being matched.
// This should always succeed for legal programs, but could fail for
// erroneous programs (e.g. the type is `[u8; const { panic!() }]`),
// so take care not to ICE if this fails.
let array_len = match pattern.ty.kind() {
ty::Array(_, len) => len.try_to_target_usize(cx.tcx),
_ => None,
};
if let Some(array_len) = array_len {
cx.prefix_slice_suffix(
&mut subpairs,
extra_data,
&place_builder,
Some(array_len),
prefix,
slice,
suffix,
);
} else {
// If the array length couldn't be determined, ignore the
// subpatterns and delayed-assert that compilation will fail.
cx.tcx.dcx().span_delayed_bug(
pattern.span,
format!(
"array length in pattern couldn't be determined for ty={:?}",
pattern.ty
),
);
}
None
}
PatKind::Slice { ref prefix, ref slice, ref suffix } => {
@ -271,6 +279,7 @@ impl<'tcx> MatchPairTree<'tcx> {
&mut subpairs,
extra_data,
&place_builder,
None,
prefix,
slice,
suffix,