Skip to content

Commit

Permalink
Rollup merge of #121820 - Nadrieril:idxpat2, r=compiler-errors
Browse files Browse the repository at this point in the history
pattern analysis: Store field indices in `DeconstructedPat` to avoid virtual wildcards

For a pattern like `Struct { field3: true, .. }`, in pattern analysis we represent it as `Struct { field1: _, field2: _, field3: true, field4: _ }`. This PR makes it so we store `Struct { field3: true, .. }` instead. This means we never have to create fake `_` patterns during lowering.

r? ``@compiler-errors``
  • Loading branch information
matthiaskrgr authored Mar 13, 2024
2 parents d3555f3 + d339bda commit 1b198ba
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 95 deletions.
12 changes: 7 additions & 5 deletions compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,9 +420,9 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> {
{
let mut redundant_subpats = redundant_subpats.clone();
// Emit lints in the order in which they occur in the file.
redundant_subpats.sort_unstable_by_key(|pat| pat.data().unwrap().span);
redundant_subpats.sort_unstable_by_key(|pat| pat.data().span);
for pat in redundant_subpats {
report_unreachable_pattern(cx, arm.arm_data, pat.data().unwrap().span, None)
report_unreachable_pattern(cx, arm.arm_data, pat.data().span, None)
}
}
}
Expand Down Expand Up @@ -905,10 +905,10 @@ fn report_arm_reachability<'p, 'tcx>(
let mut catchall = None;
for (arm, is_useful) in report.arm_usefulness.iter() {
if matches!(is_useful, Usefulness::Redundant) {
report_unreachable_pattern(cx, arm.arm_data, arm.pat.data().unwrap().span, catchall)
report_unreachable_pattern(cx, arm.arm_data, arm.pat.data().span, catchall)
}
if !arm.has_guard && catchall.is_none() && pat_is_catchall(arm.pat) {
catchall = Some(arm.pat.data().unwrap().span);
catchall = Some(arm.pat.data().span);
}
}
}
Expand All @@ -917,7 +917,9 @@ fn report_arm_reachability<'p, 'tcx>(
fn pat_is_catchall(pat: &DeconstructedPat<'_, '_>) -> bool {
match pat.ctor() {
Constructor::Wildcard => true,
Constructor::Struct | Constructor::Ref => pat.iter_fields().all(|pat| pat_is_catchall(pat)),
Constructor::Struct | Constructor::Ref => {
pat.iter_fields().all(|ipat| pat_is_catchall(&ipat.pat))
}
_ => false,
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_pattern_analysis/src/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ pub enum SliceKind {
}

impl SliceKind {
fn arity(self) -> usize {
pub fn arity(self) -> usize {
match self {
FixedLen(length) => length,
VarLen(prefix, suffix) => prefix + suffix,
Expand Down Expand Up @@ -462,7 +462,7 @@ impl Slice {
Slice { array_len, kind }
}

pub(crate) fn arity(self) -> usize {
pub fn arity(self) -> usize {
self.kind.arity()
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_pattern_analysis/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'p, 'tcx>(
};

use rustc_errors::LintDiagnostic;
let mut err = rcx.tcx.dcx().struct_span_warn(arm.pat.data().unwrap().span, "");
let mut err = rcx.tcx.dcx().struct_span_warn(arm.pat.data().span, "");
err.primary_message(decorator.msg());
decorator.decorate_lint(&mut err);
err.emit();
Expand Down
122 changes: 71 additions & 51 deletions compiler/rustc_pattern_analysis/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,42 @@ impl PatId {
}
}

/// A pattern with an index denoting which field it corresponds to.
pub struct IndexedPat<Cx: TypeCx> {
pub idx: usize,
pub pat: DeconstructedPat<Cx>,
}

/// Values and patterns can be represented as a constructor applied to some fields. This represents
/// a pattern in this form. A `DeconstructedPat` will almost always come from user input; the only
/// exception are some `Wildcard`s introduced during pattern lowering.
pub struct DeconstructedPat<Cx: TypeCx> {
ctor: Constructor<Cx>,
fields: Vec<DeconstructedPat<Cx>>,
fields: Vec<IndexedPat<Cx>>,
/// The number of fields in this pattern. E.g. if the pattern is `SomeStruct { field12: true, ..
/// }` this would be the total number of fields of the struct.
/// This is also the same as `self.ctor.arity(self.ty)`.
arity: usize,
ty: Cx::Ty,
/// Extra data to store in a pattern. `None` if the pattern is a wildcard that does not
/// correspond to a user-supplied pattern.
data: Option<Cx::PatData>,
/// Extra data to store in a pattern.
data: Cx::PatData,
/// Globally-unique id used to track usefulness at the level of subpatterns.
pub(crate) uid: PatId,
}

impl<Cx: TypeCx> DeconstructedPat<Cx> {
pub fn wildcard(ty: Cx::Ty) -> Self {
DeconstructedPat { ctor: Wildcard, fields: Vec::new(), ty, data: None, uid: PatId::new() }
}

pub fn new(
ctor: Constructor<Cx>,
fields: Vec<DeconstructedPat<Cx>>,
fields: Vec<IndexedPat<Cx>>,
arity: usize,
ty: Cx::Ty,
data: Cx::PatData,
) -> Self {
DeconstructedPat { ctor, fields, ty, data: Some(data), uid: PatId::new() }
DeconstructedPat { ctor, fields, arity, ty, data, uid: PatId::new() }
}

pub fn at_index(self, idx: usize) -> IndexedPat<Cx> {
IndexedPat { idx, pat: self }
}

pub(crate) fn is_or_pat(&self) -> bool {
Expand All @@ -58,13 +68,15 @@ impl<Cx: TypeCx> DeconstructedPat<Cx> {
pub fn ty(&self) -> &Cx::Ty {
&self.ty
}
/// Returns the extra data stored in a pattern. Returns `None` if the pattern is a wildcard that
/// does not correspond to a user-supplied pattern.
pub fn data(&self) -> Option<&Cx::PatData> {
self.data.as_ref()
/// Returns the extra data stored in a pattern.
pub fn data(&self) -> &Cx::PatData {
&self.data
}
pub fn arity(&self) -> usize {
self.arity
}

pub fn iter_fields<'a>(&'a self) -> impl Iterator<Item = &'a DeconstructedPat<Cx>> {
pub fn iter_fields<'a>(&'a self) -> impl Iterator<Item = &'a IndexedPat<Cx>> {
self.fields.iter()
}

Expand All @@ -73,36 +85,40 @@ impl<Cx: TypeCx> DeconstructedPat<Cx> {
pub(crate) fn specialize<'a>(
&'a self,
other_ctor: &Constructor<Cx>,
ctor_arity: usize,
other_ctor_arity: usize,
) -> SmallVec<[PatOrWild<'a, Cx>; 2]> {
let wildcard_sub_tys = || (0..ctor_arity).map(|_| PatOrWild::Wild).collect();
match (&self.ctor, other_ctor) {
// Return a wildcard for each field of `other_ctor`.
(Wildcard, _) => wildcard_sub_tys(),
if matches!(other_ctor, PrivateUninhabited) {
// Skip this column.
(_, PrivateUninhabited) => smallvec![],
// The only non-trivial case: two slices of different arity. `other_slice` is
// guaranteed to have a larger arity, so we fill the middle part with enough
// wildcards to reach the length of the new, larger slice.
(
&Slice(self_slice @ Slice { kind: SliceKind::VarLen(prefix, suffix), .. }),
&Slice(other_slice),
) if self_slice.arity() != other_slice.arity() => {
// Start with a slice of wildcards of the appropriate length.
let mut fields: SmallVec<[_; 2]> = wildcard_sub_tys();
// Fill in the fields from both ends.
let new_arity = fields.len();
for i in 0..prefix {
fields[i] = PatOrWild::Pat(&self.fields[i]);
return smallvec![];
}

// Start with a slice of wildcards of the appropriate length.
let mut fields: SmallVec<[_; 2]> = (0..other_ctor_arity).map(|_| PatOrWild::Wild).collect();
// Fill `fields` with our fields. The arities are known to be compatible.
match self.ctor {
// The only non-trivial case: two slices of different arity. `other_ctor` is guaranteed
// to have a larger arity, so we adjust the indices of the patterns in the suffix so
// that they are correctly positioned in the larger slice.
Slice(Slice { kind: SliceKind::VarLen(prefix, _), .. })
if self.arity != other_ctor_arity =>
{
for ipat in &self.fields {
let new_idx = if ipat.idx < prefix {
ipat.idx
} else {
// Adjust the indices in the suffix.
ipat.idx + other_ctor_arity - self.arity
};
fields[new_idx] = PatOrWild::Pat(&ipat.pat);
}
for i in 0..suffix {
fields[new_arity - 1 - i] =
PatOrWild::Pat(&self.fields[self.fields.len() - 1 - i]);
}
_ => {
for ipat in &self.fields {
fields[ipat.idx] = PatOrWild::Pat(&ipat.pat);
}
fields
}
_ => self.fields.iter().map(PatOrWild::Pat).collect(),
}
fields
}

/// Walk top-down and call `it` in each place where a pattern occurs
Expand All @@ -114,7 +130,7 @@ impl<Cx: TypeCx> DeconstructedPat<Cx> {
}

for p in self.iter_fields() {
p.walk(it)
p.pat.walk(it)
}
}
}
Expand All @@ -134,14 +150,19 @@ impl<Cx: TypeCx> fmt::Debug for DeconstructedPat<Cx> {
};
let mut start_or_comma = || start_or_continue(", ");

let mut fields: Vec<_> = (0..self.arity).map(|_| PatOrWild::Wild).collect();
for ipat in self.iter_fields() {
fields[ipat.idx] = PatOrWild::Pat(&ipat.pat);
}

match pat.ctor() {
Struct | Variant(_) | UnionField => {
Cx::write_variant_name(f, pat)?;
// Without `cx`, we can't know which field corresponds to which, so we can't
// get the names of the fields. Instead we just display everything as a tuple
// struct, which should be good enough.
write!(f, "(")?;
for p in pat.iter_fields() {
for p in fields {
write!(f, "{}", start_or_comma())?;
write!(f, "{p:?}")?;
}
Expand All @@ -151,25 +172,23 @@ impl<Cx: TypeCx> fmt::Debug for DeconstructedPat<Cx> {
// be careful to detect strings here. However a string literal pattern will never
// be reported as a non-exhaustiveness witness, so we can ignore this issue.
Ref => {
let subpattern = pat.iter_fields().next().unwrap();
write!(f, "&{:?}", subpattern)
write!(f, "&{:?}", &fields[0])
}
Slice(slice) => {
let mut subpatterns = pat.iter_fields();
write!(f, "[")?;
match slice.kind {
SliceKind::FixedLen(_) => {
for p in subpatterns {
for p in fields {
write!(f, "{}{:?}", start_or_comma(), p)?;
}
}
SliceKind::VarLen(prefix_len, _) => {
for p in subpatterns.by_ref().take(prefix_len) {
for p in &fields[..prefix_len] {
write!(f, "{}{:?}", start_or_comma(), p)?;
}
write!(f, "{}", start_or_comma())?;
write!(f, "..")?;
for p in subpatterns {
for p in &fields[prefix_len..] {
write!(f, "{}{:?}", start_or_comma(), p)?;
}
}
Expand All @@ -184,7 +203,7 @@ impl<Cx: TypeCx> fmt::Debug for DeconstructedPat<Cx> {
Str(value) => write!(f, "{value:?}"),
Opaque(..) => write!(f, "<constant pattern>"),
Or => {
for pat in pat.iter_fields() {
for pat in fields {
write!(f, "{}{:?}", start_or_continue(" | "), pat)?;
}
Ok(())
Expand Down Expand Up @@ -242,9 +261,10 @@ impl<'p, Cx: TypeCx> PatOrWild<'p, Cx> {
/// Expand this (possibly-nested) or-pattern into its alternatives.
pub(crate) fn flatten_or_pat(self) -> SmallVec<[Self; 1]> {
match self {
PatOrWild::Pat(pat) if pat.is_or_pat() => {
pat.iter_fields().flat_map(|p| PatOrWild::Pat(p).flatten_or_pat()).collect()
}
PatOrWild::Pat(pat) if pat.is_or_pat() => pat
.iter_fields()
.flat_map(|ipat| PatOrWild::Pat(&ipat.pat).flatten_or_pat())
.collect(),
_ => smallvec![self],
}
}
Expand Down
Loading

0 comments on commit 1b198ba

Please sign in to comment.