about summary refs log tree commit diff
path: root/compiler/rustc_pattern_analysis/src/rustc
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_pattern_analysis/src/rustc')
-rw-r--r--compiler/rustc_pattern_analysis/src/rustc/print.rs160
1 files changed, 160 insertions, 0 deletions
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..17e389df17e
--- /dev/null
+++ b/compiler/rustc_pattern_analysis/src/rustc/print.rs
@@ -0,0 +1,160 @@
+//! 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::bug;
+use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt};
+use rustc_span::sym;
+use rustc_target::abi::{FieldIdx, VariantIdx};
+
+#[derive(Clone, Debug)]
+pub(crate) struct FieldPat {
+    pub(crate) field: FieldIdx,
+    pub(crate) pattern: String,
+    pub(crate) is_wildcard: bool,
+}
+
+/// Returns a closure that will return `""` when called the first time,
+/// and then return `", "` when called any subsequent times.
+/// Useful for printing comma-separated lists.
+fn start_or_comma() -> impl FnMut() -> &'static str {
+    let mut first = true;
+    move || {
+        if first {
+            first = false;
+            ""
+        } else {
+            ", "
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum EnumInfo<'tcx> {
+    Enum { adt_def: AdtDef<'tcx>, variant_index: VariantIdx },
+    NotEnum,
+}
+
+pub(crate) fn write_struct_like<'tcx>(
+    f: &mut impl fmt::Write,
+    tcx: TyCtxt<'_>,
+    ty: Ty<'tcx>,
+    enum_info: &EnumInfo<'tcx>,
+    subpatterns: &[FieldPat],
+) -> fmt::Result {
+    let variant_and_name = match *enum_info {
+        EnumInfo::Enum { adt_def, variant_index } => {
+            let variant = adt_def.variant(variant_index);
+            let adt_did = adt_def.did();
+            let name = if tcx.is_diagnostic_item(sym::Option, adt_did)
+                || tcx.is_diagnostic_item(sym::Result, adt_did)
+            {
+                variant.name.to_string()
+            } else {
+                format!("{}::{}", tcx.def_path_str(adt_def.did()), variant.name)
+            };
+            Some((variant, name))
+        }
+        EnumInfo::NotEnum => ty.ty_adt_def().and_then(|adt_def| {
+            Some((adt_def.non_enum_variant(), tcx.def_path_str(adt_def.did())))
+        }),
+    };
+
+    let mut start_or_comma = start_or_comma();
+
+    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 &FieldPat { field, ref pattern, is_wildcard } in subpatterns {
+                if is_wildcard {
+                    continue;
+                }
+                let field_name = variant.fields[field].name;
+                write!(f, "{}{field_name}: {pattern}", start_or_comma())?;
+                printed += 1;
+            }
+
+            let is_union = 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(())
+}
+
+pub(crate) fn write_ref_like<'tcx>(
+    f: &mut impl fmt::Write,
+    ty: Ty<'tcx>,
+    subpattern: &str,
+) -> fmt::Result {
+    match ty.kind() {
+        ty::Ref(_, _, mutbl) => {
+            write!(f, "&{}", mutbl.prefix_str())?;
+        }
+        _ => bug!("{ty} is a bad ref pattern type"),
+    }
+    write!(f, "{subpattern}")
+}
+
+pub(crate) fn write_slice_like(
+    f: &mut impl fmt::Write,
+    prefix: &[String],
+    has_dot_dot: bool,
+    suffix: &[String],
+) -> fmt::Result {
+    let mut start_or_comma = start_or_comma();
+    write!(f, "[")?;
+    for p in prefix.iter() {
+        write!(f, "{}{}", start_or_comma(), p)?;
+    }
+    if has_dot_dot {
+        write!(f, "{}..", start_or_comma())?;
+    }
+    for p in suffix.iter() {
+        write!(f, "{}{}", start_or_comma(), p)?;
+    }
+    write!(f, "]")
+}