summary refs log tree commit diff
path: root/compiler/rustc_pattern_analysis/src
diff options
context:
space:
mode:
authorZalathar <Zalathar@users.noreply.github.com>2024-07-30 22:44:02 +1000
committerZalathar <Zalathar@users.noreply.github.com>2024-07-31 16:03:27 +1000
commitdd5a8d77142562250c4780b0b8b228a0c557498e (patch)
tree14c0970093f327d8d4d34a11497e082a10161a83 /compiler/rustc_pattern_analysis/src
parenta9ea85e04422345020be7dfb7e655c911d458e6c (diff)
downloadrust-dd5a8d77142562250c4780b0b8b228a0c557498e.tar.gz
rust-dd5a8d77142562250c4780b0b8b228a0c557498e.zip
Use a separate pattern type for `rustc_pattern_analysis` diagnostics
The pattern-analysis code needs to print patterns, as part of its user-visible
diagnostics. But it never actually tries to print "real" patterns! Instead, it
only ever prints synthetic patterns that it has reconstructed from its own
internal represenations.

We can therefore simultaneously remove two obstacles to changing `thir::Pat`,
by having the pattern-analysis code use its own dedicated type for building
printable patterns, and then making `thir::Pat` not printable at all.
Diffstat (limited to 'compiler/rustc_pattern_analysis/src')
-rw-r--r--compiler/rustc_pattern_analysis/src/rustc.rs26
-rw-r--r--compiler/rustc_pattern_analysis/src/rustc/print.rs193
2 files changed, 208 insertions, 11 deletions
diff --git a/compiler/rustc_pattern_analysis/src/rustc.rs b/compiler/rustc_pattern_analysis/src/rustc.rs
index 13501720858..6290aeb2523 100644
--- a/compiler/rustc_pattern_analysis/src/rustc.rs
+++ b/compiler/rustc_pattern_analysis/src/rustc.rs
@@ -7,7 +7,7 @@ use rustc_hir::HirId;
 use rustc_index::{Idx, IndexVec};
 use rustc_middle::middle::stability::EvalResult;
 use rustc_middle::mir::{self, Const};
-use rustc_middle::thir::{self, FieldPat, Pat, PatKind, PatRange, PatRangeBoundary};
+use rustc_middle::thir::{self, Pat, PatKind, PatRange, PatRangeBoundary};
 use rustc_middle::ty::layout::IntegerExt;
 use rustc_middle::ty::{
     self, FieldDef, OpaqueTypeKey, ScalarInt, Ty, TyCtxt, TypeVisitableExt, VariantDef,
@@ -26,6 +26,8 @@ use crate::pat_column::PatternColumn;
 use crate::usefulness::{compute_match_usefulness, PlaceValidity};
 use crate::{errors, Captures, PatCx, PrivateUninhabitedField};
 
+mod print;
+
 // Re-export rustc-specific versions of all these types.
 pub type Constructor<'p, 'tcx> = crate::constructor::Constructor<RustcPatCtxt<'p, 'tcx>>;
 pub type ConstructorSet<'p, 'tcx> = crate::constructor::ConstructorSet<RustcPatCtxt<'p, 'tcx>>;
@@ -773,8 +775,9 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
         }
     }
 
-    /// Convert back to a `thir::Pat` for diagnostic purposes.
-    fn hoist_pat_range(&self, range: &IntRange, ty: RevealedTy<'tcx>) -> Pat<'tcx> {
+    /// Convert to a [`print::Pat`] for diagnostic purposes.
+    fn hoist_pat_range(&self, range: &IntRange, ty: RevealedTy<'tcx>) -> print::Pat<'tcx> {
+        use print::{Pat, PatKind};
         use MaybeInfiniteInt::*;
         let cx = self;
         let kind = if matches!((range.lo, range.hi), (NegInfinity, PosInfinity)) {
@@ -808,19 +811,20 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
             PatKind::Range(Box::new(PatRange { lo, hi, end, ty: ty.inner() }))
         };
 
-        Pat { ty: ty.inner(), span: DUMMY_SP, kind }
+        Pat { ty: ty.inner(), kind }
     }
 
     /// Prints a [`WitnessPat`] to an owned string, for diagnostic purposes.
     pub fn print_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> String {
-        // This works by converting the witness pattern back to a `thir::Pat`
+        // This works by converting the witness pattern to a `print::Pat`
         // and then printing that, but callers don't need to know that.
         self.hoist_witness_pat(pat).to_string()
     }
 
-    /// Convert back to a `thir::Pat` for diagnostic purposes. This panics for patterns that don't
+    /// Convert to a [`print::Pat`] for diagnostic purposes. This panics for patterns that don't
     /// appear in diagnostics, like float ranges.
-    fn hoist_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> Pat<'tcx> {
+    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 mut subpatterns = pat.iter_fields().map(|p| Box::new(cx.hoist_witness_pat(p)));
@@ -840,7 +844,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
                     // the pattern is a box pattern.
                     PatKind::Deref { subpattern: subpatterns.next().unwrap() }
                 }
-                ty::Adt(adt_def, args) => {
+                ty::Adt(adt_def, _args) => {
                     let variant_index = RustcPatCtxt::variant_index_for_adt(&pat.ctor(), *adt_def);
                     let subpatterns = subpatterns
                         .enumerate()
@@ -848,7 +852,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
                         .collect();
 
                     if adt_def.is_enum() {
-                        PatKind::Variant { adt_def: *adt_def, args, variant_index, subpatterns }
+                        PatKind::Variant { adt_def: *adt_def, variant_index, subpatterns }
                     } else {
                         PatKind::Leaf { subpatterns }
                     }
@@ -885,7 +889,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
                             }
                         }
                         let suffix: Box<[_]> = subpatterns.collect();
-                        let wild = Pat::wildcard_from_ty(pat.ty().inner());
+                        let wild = Pat { ty: pat.ty().inner(), kind: PatKind::Wild };
                         PatKind::Slice {
                             prefix: prefix.into_boxed_slice(),
                             slice: Some(Box::new(wild)),
@@ -906,7 +910,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
             }
         };
 
-        Pat { ty: pat.ty().inner(), span: DUMMY_SP, kind }
+        Pat { ty: pat.ty().inner(), kind }
     }
 }
 
diff --git a/compiler/rustc_pattern_analysis/src/rustc/print.rs b/compiler/rustc_pattern_analysis/src/rustc/print.rs
new file mode 100644
index 00000000000..4b76764e8b1
--- /dev/null
+++ b/compiler/rustc_pattern_analysis/src/rustc/print.rs
@@ -0,0 +1,193 @@
+//! Pattern analysis sometimes wants to print patterns as part of a user-visible
+//! diagnostic.
+//!
+//! Historically it did so by creating a synthetic [`thir::Pat`](rustc_middle::thir::Pat)
+//! and printing that, but doing so was making it hard to modify the THIR pattern
+//! representation for other purposes.
+//!
+//! So this module contains a forked copy of `thir::Pat` that is used _only_
+//! for diagnostics, and has been partly simplified to remove things that aren't
+//! needed for printing.
+
+use std::fmt;
+
+use rustc_middle::thir::PatRange;
+use rustc_middle::ty::{self, AdtDef, Ty};
+use rustc_middle::{bug, mir};
+use rustc_span::sym;
+use rustc_target::abi::{FieldIdx, VariantIdx};
+
+#[derive(Clone, Debug)]
+pub(crate) struct FieldPat<'tcx> {
+    pub(crate) field: FieldIdx,
+    pub(crate) pattern: Box<Pat<'tcx>>,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Pat<'tcx> {
+    pub(crate) ty: Ty<'tcx>,
+    pub(crate) kind: PatKind<'tcx>,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum PatKind<'tcx> {
+    Wild,
+
+    Variant {
+        adt_def: AdtDef<'tcx>,
+        variant_index: VariantIdx,
+        subpatterns: Vec<FieldPat<'tcx>>,
+    },
+
+    Leaf {
+        subpatterns: Vec<FieldPat<'tcx>>,
+    },
+
+    Deref {
+        subpattern: Box<Pat<'tcx>>,
+    },
+
+    Constant {
+        value: mir::Const<'tcx>,
+    },
+
+    Range(Box<PatRange<'tcx>>),
+
+    Slice {
+        prefix: Box<[Box<Pat<'tcx>>]>,
+        slice: Option<Box<Pat<'tcx>>>,
+        suffix: Box<[Box<Pat<'tcx>>]>,
+    },
+
+    Never,
+}
+
+impl<'tcx> fmt::Display for Pat<'tcx> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        // Printing lists is a chore.
+        let mut first = true;
+        let mut start_or_continue = |s| {
+            if first {
+                first = false;
+                ""
+            } else {
+                s
+            }
+        };
+        let mut start_or_comma = || start_or_continue(", ");
+
+        match self.kind {
+            PatKind::Wild => write!(f, "_"),
+            PatKind::Never => write!(f, "!"),
+            PatKind::Variant { ref subpatterns, .. } | PatKind::Leaf { ref subpatterns } => {
+                let variant_and_name = match self.kind {
+                    PatKind::Variant { adt_def, variant_index, .. } => ty::tls::with(|tcx| {
+                        let variant = adt_def.variant(variant_index);
+                        let adt_did = adt_def.did();
+                        let name = if tcx.get_diagnostic_item(sym::Option) == Some(adt_did)
+                            || tcx.get_diagnostic_item(sym::Result) == Some(adt_did)
+                        {
+                            variant.name.to_string()
+                        } else {
+                            format!("{}::{}", tcx.def_path_str(adt_def.did()), variant.name)
+                        };
+                        Some((variant, name))
+                    }),
+                    _ => self.ty.ty_adt_def().and_then(|adt_def| {
+                        if !adt_def.is_enum() {
+                            ty::tls::with(|tcx| {
+                                Some((adt_def.non_enum_variant(), tcx.def_path_str(adt_def.did())))
+                            })
+                        } else {
+                            None
+                        }
+                    }),
+                };
+
+                if let Some((variant, name)) = &variant_and_name {
+                    write!(f, "{name}")?;
+
+                    // Only for Adt we can have `S {...}`,
+                    // which we handle separately here.
+                    if variant.ctor.is_none() {
+                        write!(f, " {{ ")?;
+
+                        let mut printed = 0;
+                        for p in subpatterns {
+                            if let PatKind::Wild = p.pattern.kind {
+                                continue;
+                            }
+                            let name = variant.fields[p.field].name;
+                            write!(f, "{}{}: {}", start_or_comma(), name, p.pattern)?;
+                            printed += 1;
+                        }
+
+                        let is_union = self.ty.ty_adt_def().is_some_and(|adt| adt.is_union());
+                        if printed < variant.fields.len() && (!is_union || printed == 0) {
+                            write!(f, "{}..", start_or_comma())?;
+                        }
+
+                        return write!(f, " }}");
+                    }
+                }
+
+                let num_fields =
+                    variant_and_name.as_ref().map_or(subpatterns.len(), |(v, _)| v.fields.len());
+                if num_fields != 0 || variant_and_name.is_none() {
+                    write!(f, "(")?;
+                    for i in 0..num_fields {
+                        write!(f, "{}", start_or_comma())?;
+
+                        // Common case: the field is where we expect it.
+                        if let Some(p) = subpatterns.get(i) {
+                            if p.field.index() == i {
+                                write!(f, "{}", p.pattern)?;
+                                continue;
+                            }
+                        }
+
+                        // Otherwise, we have to go looking for it.
+                        if let Some(p) = subpatterns.iter().find(|p| p.field.index() == i) {
+                            write!(f, "{}", p.pattern)?;
+                        } else {
+                            write!(f, "_")?;
+                        }
+                    }
+                    write!(f, ")")?;
+                }
+
+                Ok(())
+            }
+            PatKind::Deref { ref subpattern } => {
+                match self.ty.kind() {
+                    ty::Adt(def, _) if def.is_box() => write!(f, "box ")?,
+                    ty::Ref(_, _, mutbl) => {
+                        write!(f, "&{}", mutbl.prefix_str())?;
+                    }
+                    _ => bug!("{} is a bad Deref pattern type", self.ty),
+                }
+                write!(f, "{subpattern}")
+            }
+            PatKind::Constant { value } => write!(f, "{value}"),
+            PatKind::Range(ref range) => write!(f, "{range}"),
+            PatKind::Slice { ref prefix, ref slice, ref suffix } => {
+                write!(f, "[")?;
+                for p in prefix.iter() {
+                    write!(f, "{}{}", start_or_comma(), p)?;
+                }
+                if let Some(ref slice) = *slice {
+                    write!(f, "{}", start_or_comma())?;
+                    match slice.kind {
+                        PatKind::Wild => {}
+                        _ => write!(f, "{slice}")?,
+                    }
+                    write!(f, "..")?;
+                }
+                for p in suffix.iter() {
+                    write!(f, "{}{}", start_or_comma(), p)?;
+                }
+                write!(f, "]")
+            }
+        }
+    }
+}