rustc_mir_dataflow/framework/
fmt.rs

1//! Custom formatting traits used when outputting Graphviz diagrams with the results of a dataflow
2//! analysis.
3
4use std::fmt;
5
6use rustc_index::Idx;
7use rustc_index::bit_set::{ChunkedBitSet, DenseBitSet, MixedBitSet};
8
9use super::lattice::MaybeReachable;
10
11/// An extension to `fmt::Debug` for data that can be better printed with some auxiliary data `C`.
12pub trait DebugWithContext<C>: Eq + fmt::Debug {
13    fn fmt_with(&self, _ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        fmt::Debug::fmt(self, f)
15    }
16
17    /// Print the difference between `self` and `old`.
18    ///
19    /// This should print nothing if `self == old`.
20    ///
21    /// `+` and `-` are typically used to indicate differences. However, these characters are
22    /// fairly common and may be needed to print a types representation. If using them to indicate
23    /// a diff, prefix them with the "Unit Separator"  control character (␟  U+001F).
24    fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        if self == old {
26            return Ok(());
27        }
28
29        write!(f, "\u{001f}+")?;
30        self.fmt_with(ctxt, f)?;
31
32        if f.alternate() {
33            write!(f, "\n")?;
34        } else {
35            write!(f, "\t")?;
36        }
37
38        write!(f, "\u{001f}-")?;
39        old.fmt_with(ctxt, f)
40    }
41}
42
43/// Implements `fmt::Debug` by deferring to `<T as DebugWithContext<C>>::fmt_with`.
44pub struct DebugWithAdapter<'a, T, C> {
45    pub this: T,
46    pub ctxt: &'a C,
47}
48
49impl<T, C> fmt::Debug for DebugWithAdapter<'_, T, C>
50where
51    T: DebugWithContext<C>,
52{
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        self.this.fmt_with(self.ctxt, f)
55    }
56}
57
58/// Implements `fmt::Debug` by deferring to `<T as DebugWithContext<C>>::fmt_diff_with`.
59pub struct DebugDiffWithAdapter<'a, T, C> {
60    pub new: T,
61    pub old: T,
62    pub ctxt: &'a C,
63}
64
65impl<T, C> fmt::Debug for DebugDiffWithAdapter<'_, T, C>
66where
67    T: DebugWithContext<C>,
68{
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        self.new.fmt_diff_with(&self.old, self.ctxt, f)
71    }
72}
73
74// Impls
75
76impl<T, C> DebugWithContext<C> for DenseBitSet<T>
77where
78    T: Idx + DebugWithContext<C>,
79{
80    fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.debug_set().entries(self.iter().map(|i| DebugWithAdapter { this: i, ctxt })).finish()
82    }
83
84    fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        let size = self.domain_size();
86        assert_eq!(size, old.domain_size());
87
88        let mut set_in_self = MixedBitSet::new_empty(size);
89        let mut cleared_in_self = MixedBitSet::new_empty(size);
90
91        for i in (0..size).map(T::new) {
92            match (self.contains(i), old.contains(i)) {
93                (true, false) => set_in_self.insert(i),
94                (false, true) => cleared_in_self.insert(i),
95                _ => continue,
96            };
97        }
98
99        fmt_diff(&set_in_self, &cleared_in_self, ctxt, f)
100    }
101}
102
103impl<T, C> DebugWithContext<C> for ChunkedBitSet<T>
104where
105    T: Idx + DebugWithContext<C>,
106{
107    fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        f.debug_set().entries(self.iter().map(|i| DebugWithAdapter { this: i, ctxt })).finish()
109    }
110
111    fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        let size = self.domain_size();
113        assert_eq!(size, old.domain_size());
114
115        let mut set_in_self = MixedBitSet::new_empty(size);
116        let mut cleared_in_self = MixedBitSet::new_empty(size);
117
118        for i in (0..size).map(T::new) {
119            match (self.contains(i), old.contains(i)) {
120                (true, false) => set_in_self.insert(i),
121                (false, true) => cleared_in_self.insert(i),
122                _ => continue,
123            };
124        }
125
126        fmt_diff(&set_in_self, &cleared_in_self, ctxt, f)
127    }
128}
129
130impl<T, C> DebugWithContext<C> for MixedBitSet<T>
131where
132    T: Idx + DebugWithContext<C>,
133{
134    fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match self {
136            MixedBitSet::Small(set) => set.fmt_with(ctxt, f),
137            MixedBitSet::Large(set) => set.fmt_with(ctxt, f),
138        }
139    }
140
141    fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        match (self, old) {
143            (MixedBitSet::Small(set), MixedBitSet::Small(old)) => set.fmt_diff_with(old, ctxt, f),
144            (MixedBitSet::Large(set), MixedBitSet::Large(old)) => set.fmt_diff_with(old, ctxt, f),
145            _ => panic!("MixedBitSet size mismatch"),
146        }
147    }
148}
149
150impl<S, C> DebugWithContext<C> for MaybeReachable<S>
151where
152    S: DebugWithContext<C>,
153{
154    fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        match self {
156            MaybeReachable::Unreachable => {
157                write!(f, "unreachable")
158            }
159            MaybeReachable::Reachable(set) => set.fmt_with(ctxt, f),
160        }
161    }
162
163    fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        match (self, old) {
165            (MaybeReachable::Unreachable, MaybeReachable::Unreachable) => Ok(()),
166            (MaybeReachable::Unreachable, MaybeReachable::Reachable(set)) => {
167                write!(f, "\u{001f}+")?;
168                set.fmt_with(ctxt, f)
169            }
170            (MaybeReachable::Reachable(set), MaybeReachable::Unreachable) => {
171                write!(f, "\u{001f}-")?;
172                set.fmt_with(ctxt, f)
173            }
174            (MaybeReachable::Reachable(this), MaybeReachable::Reachable(old)) => {
175                this.fmt_diff_with(old, ctxt, f)
176            }
177        }
178    }
179}
180
181fn fmt_diff<T, C>(
182    inserted: &MixedBitSet<T>,
183    removed: &MixedBitSet<T>,
184    ctxt: &C,
185    f: &mut fmt::Formatter<'_>,
186) -> fmt::Result
187where
188    T: Idx + DebugWithContext<C>,
189{
190    let mut first = true;
191    for idx in inserted.iter() {
192        let delim = if first {
193            "\u{001f}+"
194        } else if f.alternate() {
195            "\n\u{001f}+"
196        } else {
197            ", "
198        };
199
200        write!(f, "{delim}")?;
201        idx.fmt_with(ctxt, f)?;
202        first = false;
203    }
204
205    if !f.alternate() {
206        first = true;
207        if !inserted.is_empty() && !removed.is_empty() {
208            write!(f, "\t")?;
209        }
210    }
211
212    for idx in removed.iter() {
213        let delim = if first {
214            "\u{001f}-"
215        } else if f.alternate() {
216            "\n\u{001f}-"
217        } else {
218            ", "
219        };
220
221        write!(f, "{delim}")?;
222        idx.fmt_with(ctxt, f)?;
223        first = false;
224    }
225
226    Ok(())
227}
228
229impl<T, C> DebugWithContext<C> for &'_ T
230where
231    T: DebugWithContext<C>,
232{
233    fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        (*self).fmt_with(ctxt, f)
235    }
236
237    fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238        (*self).fmt_diff_with(*old, ctxt, f)
239    }
240}
241
242impl<C> DebugWithContext<C> for rustc_middle::mir::Local {}
243impl<C> DebugWithContext<C> for crate::move_paths::InitIndex {}
244
245impl<'tcx, C> DebugWithContext<C> for crate::move_paths::MovePathIndex
246where
247    C: crate::move_paths::HasMoveData<'tcx>,
248{
249    fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        write!(f, "{}", ctxt.move_data().move_paths[*self])
251    }
252}