| //! Custom formatting traits used when outputting Graphviz diagrams with the results of a dataflow | |
| //! analysis. | |
| use std::fmt; | |
| use rustc_index::Idx; | |
| use rustc_index::bit_set::{ChunkedBitSet, DenseBitSet, MixedBitSet}; | |
| use super::lattice::MaybeReachable; | |
| /// An extension to `fmt::Debug` for data that can be better printed with some auxiliary data `C`. | |
| pub trait DebugWithContext<C>: 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 `<T as DebugWithContext<C>>::fmt_with`. | |
| pub(crate) struct DebugWithAdapter<'a, T, C> { | |
| pub(crate) this: T, | |
| pub(crate) ctxt: &'a C, | |
| } | |
| impl<T, C> fmt::Debug for DebugWithAdapter<'_, T, C> | |
| where | |
| T: DebugWithContext<C>, | |
| { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| self.this.fmt_with(self.ctxt, f) | |
| } | |
| } | |
| /// Implements `fmt::Debug` by deferring to `<T as DebugWithContext<C>>::fmt_diff_with`. | |
| pub(crate) struct DebugDiffWithAdapter<'a, T, C> { | |
| pub(crate) new: T, | |
| pub(crate) old: T, | |
| pub(crate) ctxt: &'a C, | |
| } | |
| impl<T, C> fmt::Debug for DebugDiffWithAdapter<'_, T, C> | |
| where | |
| T: DebugWithContext<C>, | |
| { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| self.new.fmt_diff_with(&self.old, self.ctxt, f) | |
| } | |
| } | |
| // Impls | |
| impl<T, C> DebugWithContext<C> for DenseBitSet<T> | |
| where | |
| T: Idx + DebugWithContext<C>, | |
| { | |
| 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 = MixedBitSet::new_empty(size); | |
| let mut cleared_in_self = MixedBitSet::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<T, C> DebugWithContext<C> for ChunkedBitSet<T> | |
| where | |
| T: Idx + DebugWithContext<C>, | |
| { | |
| 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 = MixedBitSet::new_empty(size); | |
| let mut cleared_in_self = MixedBitSet::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<T, C> DebugWithContext<C> for MixedBitSet<T> | |
| where | |
| T: Idx + DebugWithContext<C>, | |
| { | |
| fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| MixedBitSet::Small(set) => set.fmt_with(ctxt, f), | |
| MixedBitSet::Large(set) => set.fmt_with(ctxt, f), | |
| } | |
| } | |
| fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match (self, old) { | |
| (MixedBitSet::Small(set), MixedBitSet::Small(old)) => set.fmt_diff_with(old, ctxt, f), | |
| (MixedBitSet::Large(set), MixedBitSet::Large(old)) => set.fmt_diff_with(old, ctxt, f), | |
| _ => panic!("MixedBitSet size mismatch"), | |
| } | |
| } | |
| } | |
| impl<S, C> DebugWithContext<C> for MaybeReachable<S> | |
| where | |
| S: DebugWithContext<C>, | |
| { | |
| fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| MaybeReachable::Unreachable => { | |
| write!(f, "unreachable") | |
| } | |
| MaybeReachable::Reachable(set) => set.fmt_with(ctxt, f), | |
| } | |
| } | |
| fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match (self, old) { | |
| (MaybeReachable::Unreachable, MaybeReachable::Unreachable) => Ok(()), | |
| (MaybeReachable::Unreachable, MaybeReachable::Reachable(set)) => { | |
| write!(f, "\u{001f}+")?; | |
| set.fmt_with(ctxt, f) | |
| } | |
| (MaybeReachable::Reachable(set), MaybeReachable::Unreachable) => { | |
| write!(f, "\u{001f}-")?; | |
| set.fmt_with(ctxt, f) | |
| } | |
| (MaybeReachable::Reachable(this), MaybeReachable::Reachable(old)) => { | |
| this.fmt_diff_with(old, ctxt, f) | |
| } | |
| } | |
| } | |
| } | |
| fn fmt_diff<T, C>( | |
| inserted: &MixedBitSet<T>, | |
| removed: &MixedBitSet<T>, | |
| ctxt: &C, | |
| f: &mut fmt::Formatter<'_>, | |
| ) -> fmt::Result | |
| where | |
| T: Idx + DebugWithContext<C>, | |
| { | |
| 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<T, C> DebugWithContext<C> for &'_ T | |
| where | |
| T: DebugWithContext<C>, | |
| { | |
| 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<C> DebugWithContext<C> for rustc_middle::mir::Local {} | |
| impl<C> DebugWithContext<C> for crate::move_paths::InitIndex {} | |
| impl<'tcx, C> DebugWithContext<C> 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]) | |
| } | |
| } |