Simplify hoisting of array/slice patterns

We can replace some tricky iterator-mutation code with a much simpler version
that uses `while let` to shrink a slice.

We also check whether a subpattern would be a wildcard _before_ hoisting it,
which will be very useful when trying to get rid of `print::PatKind` later.
This commit is contained in:
Zalathar 2024-08-02 20:55:53 +10:00
parent c764bea0c3
commit a245bfa617
2 changed files with 44 additions and 30 deletions

View File

@ -5,6 +5,7 @@
// tidy-alphabetical-start
#![allow(rustc::diagnostic_outside_of_impl)]
#![allow(rustc::untranslatable_diagnostic)]
#![cfg_attr(feature = "rustc", feature(let_chains))]
// tidy-alphabetical-end
pub mod constructor;

View File

@ -827,7 +827,6 @@ pub fn print_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> String {
fn hoist_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> print::Pat<'tcx> {
use print::{FieldPat, Pat, PatKind};
let cx = self;
let is_wildcard = |pat: &Pat<'_>| matches!(pat.kind, PatKind::Wild);
let hoist = |p| Box::new(cx.hoist_witness_pat(p));
let mut subpatterns = pat.iter_fields().map(hoist);
let kind = match pat.ctor() {
@ -862,37 +861,35 @@ fn hoist_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> print::Pat<'tcx> {
// ignore this issue.
Ref => PatKind::Deref { subpattern: subpatterns.next().unwrap() },
Slice(slice) => {
match slice.kind {
SliceKind::FixedLen(_) => PatKind::Slice {
prefix: subpatterns.collect(),
has_dot_dot: false,
suffix: Box::new([]),
},
SliceKind::VarLen(prefix, _) => {
let mut subpatterns = subpatterns.peekable();
let mut prefix: Vec<_> = subpatterns.by_ref().take(prefix).collect();
if slice.array_len.is_some() {
// Improves diagnostics a bit: if the type is a known-size array, instead
// of reporting `[x, _, .., _, y]`, we prefer to report `[x, .., y]`.
// This is incorrect if the size is not known, since `[_, ..]` captures
// arrays of lengths `>= 1` whereas `[..]` captures any length.
while !prefix.is_empty() && is_wildcard(prefix.last().unwrap()) {
prefix.pop();
}
while subpatterns.peek().is_some()
&& is_wildcard(subpatterns.peek().unwrap())
{
subpatterns.next();
}
}
let suffix: Box<[_]> = subpatterns.collect();
PatKind::Slice {
prefix: prefix.into_boxed_slice(),
has_dot_dot: true,
suffix,
}
let (prefix_len, has_dot_dot) = match slice.kind {
SliceKind::FixedLen(len) => (len, false),
SliceKind::VarLen(prefix_len, _) => (prefix_len, true),
};
let (mut prefix, mut suffix) = pat.fields.split_at(prefix_len);
// If the pattern contains a `..`, but is applied to values of statically-known
// length (arrays), then we can slightly simplify diagnostics by merging any
// adjacent wildcard patterns into the `..`: `[x, _, .., _, y]` => `[x, .., y]`.
// (This simplification isn't allowed for slice values, because in that case
// `[x, .., y]` would match some slices that `[x, _, .., _, y]` would not.)
if has_dot_dot && slice.array_len.is_some() {
while let [rest @ .., last] = prefix
&& would_print_as_wildcard(cx.tcx, last)
{
prefix = rest;
}
while let [first, rest @ ..] = suffix
&& would_print_as_wildcard(cx.tcx, first)
{
suffix = rest;
}
}
let prefix = prefix.iter().map(hoist).collect();
let suffix = suffix.iter().map(hoist).collect();
PatKind::Slice { prefix, has_dot_dot, suffix }
}
&Str(value) => PatKind::Constant { value },
Never if self.tcx.features().never_patterns => PatKind::Never,
@ -910,6 +907,22 @@ fn hoist_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> print::Pat<'tcx> {
}
}
/// Returns `true` if the given pattern would be printed as a wildcard (`_`).
fn would_print_as_wildcard(tcx: TyCtxt<'_>, p: &WitnessPat<'_, '_>) -> bool {
match p.ctor() {
Constructor::IntRange(IntRange {
lo: MaybeInfiniteInt::NegInfinity,
hi: MaybeInfiniteInt::PosInfinity,
})
| Constructor::Wildcard
| Constructor::NonExhaustive
| Constructor::Hidden
| Constructor::PrivateUninhabited => true,
Constructor::Never if !tcx.features().never_patterns => true,
_ => false,
}
}
impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> {
type Ty = RevealedTy<'tcx>;
type Error = ErrorGuaranteed;