rustc_pattern_analysis/rustc/
print.rs

1//! Pattern analysis sometimes wants to print patterns as part of a user-visible
2//! diagnostic.
3//!
4//! Historically it did so by creating a synthetic [`thir::Pat`](rustc_middle::thir::Pat)
5//! and printing that, but doing so was making it hard to modify the THIR pattern
6//! representation for other purposes.
7//!
8//! So this module contains a forked copy of `thir::Pat` that is used _only_
9//! for diagnostics, and has been partly simplified to remove things that aren't
10//! needed for printing.
11
12use std::fmt;
13
14use rustc_abi::{FieldIdx, VariantIdx};
15use rustc_middle::bug;
16use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt};
17use rustc_span::sym;
18
19#[derive(Clone, Debug)]
20pub(crate) struct FieldPat {
21    pub(crate) field: FieldIdx,
22    pub(crate) pattern: String,
23    pub(crate) is_wildcard: bool,
24}
25
26/// Returns a closure that will return `""` when called the first time,
27/// and then return `", "` when called any subsequent times.
28/// Useful for printing comma-separated lists.
29fn start_or_comma() -> impl FnMut() -> &'static str {
30    let mut first = true;
31    move || {
32        if first {
33            first = false;
34            ""
35        } else {
36            ", "
37        }
38    }
39}
40
41#[derive(Clone, Debug)]
42pub(crate) enum EnumInfo<'tcx> {
43    Enum { adt_def: AdtDef<'tcx>, variant_index: VariantIdx },
44    NotEnum,
45}
46
47pub(crate) fn write_struct_like<'tcx>(
48    f: &mut impl fmt::Write,
49    tcx: TyCtxt<'_>,
50    ty: Ty<'tcx>,
51    enum_info: &EnumInfo<'tcx>,
52    subpatterns: &[FieldPat],
53) -> fmt::Result {
54    let variant_and_name = match *enum_info {
55        EnumInfo::Enum { adt_def, variant_index } => {
56            let variant = adt_def.variant(variant_index);
57            let adt_did = adt_def.did();
58            let name = if tcx.is_diagnostic_item(sym::Option, adt_did)
59                || tcx.is_diagnostic_item(sym::Result, adt_did)
60            {
61                variant.name.to_string()
62            } else {
63                format!("{}::{}", tcx.def_path_str(adt_def.did()), variant.name)
64            };
65            Some((variant, name))
66        }
67        EnumInfo::NotEnum => ty.ty_adt_def().and_then(|adt_def| {
68            Some((adt_def.non_enum_variant(), tcx.def_path_str(adt_def.did())))
69        }),
70    };
71
72    let mut start_or_comma = start_or_comma();
73
74    if let Some((variant, name)) = &variant_and_name {
75        write!(f, "{name}")?;
76
77        // Only for Adt we can have `S {...}`,
78        // which we handle separately here.
79        if variant.ctor.is_none() {
80            write!(f, " {{ ")?;
81
82            let mut printed = 0;
83            for &FieldPat { field, ref pattern, is_wildcard } in subpatterns {
84                if is_wildcard {
85                    continue;
86                }
87                let field_name = variant.fields[field].name;
88                write!(f, "{}{field_name}: {pattern}", start_or_comma())?;
89                printed += 1;
90            }
91
92            let is_union = ty.ty_adt_def().is_some_and(|adt| adt.is_union());
93            if printed < variant.fields.len() && (!is_union || printed == 0) {
94                write!(f, "{}..", start_or_comma())?;
95            }
96
97            return write!(f, " }}");
98        }
99    }
100
101    let num_fields = variant_and_name.as_ref().map_or(subpatterns.len(), |(v, _)| v.fields.len());
102    if num_fields != 0 || variant_and_name.is_none() {
103        write!(f, "(")?;
104        for i in 0..num_fields {
105            write!(f, "{}", start_or_comma())?;
106
107            // Common case: the field is where we expect it.
108            if let Some(p) = subpatterns.get(i) {
109                if p.field.index() == i {
110                    write!(f, "{}", p.pattern)?;
111                    continue;
112                }
113            }
114
115            // Otherwise, we have to go looking for it.
116            if let Some(p) = subpatterns.iter().find(|p| p.field.index() == i) {
117                write!(f, "{}", p.pattern)?;
118            } else {
119                write!(f, "_")?;
120            }
121        }
122        write!(f, ")")?;
123    }
124
125    Ok(())
126}
127
128pub(crate) fn write_ref_like<'tcx>(
129    f: &mut impl fmt::Write,
130    ty: Ty<'tcx>,
131    subpattern: &str,
132) -> fmt::Result {
133    match ty.kind() {
134        ty::Ref(_, _, mutbl) => {
135            write!(f, "&{}", mutbl.prefix_str())?;
136        }
137        _ => bug!("{ty} is a bad ref pattern type"),
138    }
139    write!(f, "{subpattern}")
140}
141
142pub(crate) fn write_slice_like(
143    f: &mut impl fmt::Write,
144    prefix: &[String],
145    has_dot_dot: bool,
146    suffix: &[String],
147) -> fmt::Result {
148    let mut start_or_comma = start_or_comma();
149    write!(f, "[")?;
150    for p in prefix.iter() {
151        write!(f, "{}{}", start_or_comma(), p)?;
152    }
153    if has_dot_dot {
154        write!(f, "{}..", start_or_comma())?;
155    }
156    for p in suffix.iter() {
157        write!(f, "{}{}", start_or_comma(), p)?;
158    }
159    write!(f, "]")
160}