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 | |
12 | use std::fmt; |
13 | |
14 | use rustc_abi::{FieldIdx, VariantIdx}; |
15 | use rustc_middle::bug; |
16 | use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; |
17 | use rustc_span::sym; |
18 | |
19 | #[derive (Clone, Debug)] |
20 | pub(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. |
29 | fn start_or_comma() -> impl FnMut() -> &'static str { |
30 | let mut first: bool = true; |
31 | move || { |
32 | if first { |
33 | first = false; |
34 | "" |
35 | } else { |
36 | ", " |
37 | } |
38 | } |
39 | } |
40 | |
41 | #[derive (Clone, Debug)] |
42 | pub(crate) enum EnumInfo<'tcx> { |
43 | Enum { adt_def: AdtDef<'tcx>, variant_index: VariantIdx }, |
44 | NotEnum, |
45 | } |
46 | |
47 | pub(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 | |
128 | pub(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: &Mutability) => { |
135 | write!(f, "& {}" , mutbl.prefix_str())?; |
136 | } |
137 | _ => bug!(" {ty} is a bad ref pattern type" ), |
138 | } |
139 | write!(f, " {subpattern}" ) |
140 | } |
141 | |
142 | pub(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: impl FnMut() -> &'static str = start_or_comma(); |
149 | write!(f, "[" )?; |
150 | for p: &String 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: &String in suffix.iter() { |
157 | write!(f, " {}{}" , start_or_comma(), p)?; |
158 | } |
159 | write!(f, "]" ) |
160 | } |
161 | |