//! Custom formatting traits used when outputting Graphviz diagrams with the results of a dataflow //! analysis. use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet}; use rustc_index::vec::Idx; use std::fmt; /// An extension to `fmt::Debug` for data that can be better printed with some auxiliary data `C`. pub trait DebugWithContext: Eq + fmt::Debug { fn fmt_with(&self, _ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } /// Print the difference between `self` and `old`. /// /// This should print nothing if `self == old`. /// /// `+` and `-` are typically used to indicate differences. However, these characters are /// fairly common and may be needed to print a types representation. If using them to indicate /// a diff, prefix them with the "Unit Separator" control character (␟ U+001F). fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self == old { return Ok(()); } write!(f, "\u{001f}+")?; self.fmt_with(ctxt, f)?; if f.alternate() { write!(f, "\n")?; } else { write!(f, "\t")?; } write!(f, "\u{001f}-")?; old.fmt_with(ctxt, f) } } /// Implements `fmt::Debug` by deferring to `>::fmt_with`. pub struct DebugWithAdapter<'a, T, C> { pub this: T, pub ctxt: &'a C, } impl fmt::Debug for DebugWithAdapter<'_, T, C> where T: DebugWithContext, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.this.fmt_with(self.ctxt, f) } } /// Implements `fmt::Debug` by deferring to `>::fmt_diff_with`. pub struct DebugDiffWithAdapter<'a, T, C> { pub new: T, pub old: T, pub ctxt: &'a C, } impl fmt::Debug for DebugDiffWithAdapter<'_, T, C> where T: DebugWithContext, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.new.fmt_diff_with(&self.old, self.ctxt, f) } } // Impls impl DebugWithContext for BitSet where T: Idx + DebugWithContext, { fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_set().entries(self.iter().map(|i| DebugWithAdapter { this: i, ctxt })).finish() } fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { let size = self.domain_size(); assert_eq!(size, old.domain_size()); let mut set_in_self = HybridBitSet::new_empty(size); let mut cleared_in_self = HybridBitSet::new_empty(size); for i in (0..size).map(T::new) { match (self.contains(i), old.contains(i)) { (true, false) => set_in_self.insert(i), (false, true) => cleared_in_self.insert(i), _ => continue, }; } fmt_diff(&set_in_self, &cleared_in_self, ctxt, f) } } impl DebugWithContext for ChunkedBitSet where T: Idx + DebugWithContext, { fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_set().entries(self.iter().map(|i| DebugWithAdapter { this: i, ctxt })).finish() } fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { let size = self.domain_size(); assert_eq!(size, old.domain_size()); let mut set_in_self = HybridBitSet::new_empty(size); let mut cleared_in_self = HybridBitSet::new_empty(size); for i in (0..size).map(T::new) { match (self.contains(i), old.contains(i)) { (true, false) => set_in_self.insert(i), (false, true) => cleared_in_self.insert(i), _ => continue, }; } fmt_diff(&set_in_self, &cleared_in_self, ctxt, f) } } fn fmt_diff( inserted: &HybridBitSet, removed: &HybridBitSet, ctxt: &C, f: &mut fmt::Formatter<'_>, ) -> fmt::Result where T: Idx + DebugWithContext, { let mut first = true; for idx in inserted.iter() { let delim = if first { "\u{001f}+" } else if f.alternate() { "\n\u{001f}+" } else { ", " }; write!(f, "{delim}")?; idx.fmt_with(ctxt, f)?; first = false; } if !f.alternate() { first = true; if !inserted.is_empty() && !removed.is_empty() { write!(f, "\t")?; } } for idx in removed.iter() { let delim = if first { "\u{001f}-" } else if f.alternate() { "\n\u{001f}-" } else { ", " }; write!(f, "{delim}")?; idx.fmt_with(ctxt, f)?; first = false; } Ok(()) } impl DebugWithContext for &'_ T where T: DebugWithContext, { fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { (*self).fmt_with(ctxt, f) } fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { (*self).fmt_diff_with(*old, ctxt, f) } } impl DebugWithContext for rustc_middle::mir::Local {} impl DebugWithContext for crate::move_paths::InitIndex {} impl<'tcx, C> DebugWithContext for crate::move_paths::MovePathIndex where C: crate::move_paths::HasMoveData<'tcx>, { fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", ctxt.move_data().move_paths[*self]) } } impl DebugWithContext for crate::lattice::Dual where T: DebugWithContext, { fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { (self.0).fmt_with(ctxt, f) } fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { (self.0).fmt_diff_with(&old.0, ctxt, f) } }