cairo_lang_debug/
debug.rs

1#[cfg(test)]
2#[path = "debug_test.rs"]
3mod test;
4
5// Mostly taken from https://github.com/salsa-rs/salsa/blob/fd715619813f634fa07952f0d1b3d3a18b68fd65/components/salsa-2022/src/debug.rs
6use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
7use std::hash::Hash;
8use std::rc::Rc;
9use std::sync::Arc;
10
11use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
12use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
13
14pub trait DebugWithDb<Db: ?Sized> {
15    fn debug<'me, 'db>(&'me self, db: &'me Db) -> DebugWith<'me, Db>
16    where
17        Self: Sized + 'me,
18    {
19        DebugWith { value: BoxRef::Ref(self), db }
20    }
21
22    #[allow(dead_code)]
23    fn into_debug<'me, 'db>(self, db: &'me Db) -> DebugWith<'me, Db>
24    where
25        Self: Sized + 'me,
26    {
27        DebugWith { value: BoxRef::Box(Box::new(self)), db }
28    }
29
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result;
31}
32
33pub struct DebugWith<'me, Db: ?Sized> {
34    value: BoxRef<'me, dyn DebugWithDb<Db> + 'me>,
35    db: &'me Db,
36}
37
38enum BoxRef<'me, T: ?Sized> {
39    Box(Box<T>),
40    Ref(&'me T),
41}
42
43impl<T: ?Sized> std::ops::Deref for BoxRef<'_, T> {
44    type Target = T;
45
46    fn deref(&self) -> &Self::Target {
47        match self {
48            BoxRef::Box(b) => b,
49            BoxRef::Ref(r) => r,
50        }
51    }
52}
53
54impl<D: ?Sized> std::fmt::Debug for DebugWith<'_, D> {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        DebugWithDb::fmt(&*self.value, f, self.db)
57    }
58}
59
60impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for &T
61where
62    T: DebugWithDb<Db>,
63{
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
65        T::fmt(self, f, db)
66    }
67}
68
69impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for Box<T>
70where
71    T: DebugWithDb<Db>,
72{
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
74        T::fmt(self, f, db)
75    }
76}
77
78impl<Db: ?Sized, T> DebugWithDb<Db> for Rc<T>
79where
80    T: DebugWithDb<Db>,
81{
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
83        T::fmt(self, f, db)
84    }
85}
86
87impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for Arc<T>
88where
89    T: DebugWithDb<Db>,
90{
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
92        T::fmt(self, f, db)
93    }
94}
95
96impl<Db: ?Sized, T> DebugWithDb<Db> for [T]
97where
98    T: DebugWithDb<Db>,
99{
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
101        let elements = self.iter().map(|e| e.debug(db));
102        f.debug_list().entries(elements).finish()
103    }
104}
105
106impl<Db: ?Sized, T> DebugWithDb<Db> for Vec<T>
107where
108    T: DebugWithDb<Db>,
109{
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
111        let elements = self.iter().map(|e| e.debug(db));
112        f.debug_list().entries(elements).finish()
113    }
114}
115
116impl<Db: ?Sized, T> DebugWithDb<Db> for Option<T>
117where
118    T: DebugWithDb<Db>,
119{
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
121        let me = self.as_ref().map(|v| v.debug(db));
122        std::fmt::Debug::fmt(&me, f)
123    }
124}
125
126impl<Db: ?Sized, K, V, S> DebugWithDb<Db> for HashMap<K, V, S>
127where
128    K: DebugWithDb<Db>,
129    V: DebugWithDb<Db>,
130{
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
132        let elements = self.iter().map(|(k, v)| (k.debug(db), v.debug(db)));
133        f.debug_map().entries(elements).finish()
134    }
135}
136
137impl<Db: ?Sized, K, V> DebugWithDb<Db> for BTreeMap<K, V>
138where
139    K: DebugWithDb<Db>,
140    V: DebugWithDb<Db>,
141{
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
143        let elements = self.iter().map(|(k, v)| (k.debug(db), v.debug(db)));
144        f.debug_map().entries(elements).finish()
145    }
146}
147
148impl<Db: ?Sized, K: Hash + Eq, V> DebugWithDb<Db> for OrderedHashMap<K, V>
149where
150    K: DebugWithDb<Db>,
151    V: DebugWithDb<Db>,
152{
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
154        let elements = self.iter().map(|(k, v)| (k.debug(db), v.debug(db)));
155        f.debug_map().entries(elements).finish()
156    }
157}
158
159impl<Db: ?Sized, A> DebugWithDb<Db> for (A,)
160where
161    A: DebugWithDb<Db>,
162{
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
164        f.debug_tuple("").field(&self.0.debug(db)).finish()
165    }
166}
167
168impl<Db: ?Sized, A, B> DebugWithDb<Db> for (A, B)
169where
170    A: DebugWithDb<Db>,
171    B: DebugWithDb<Db>,
172{
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
174        f.debug_tuple("").field(&self.0.debug(db)).field(&self.1.debug(db)).finish()
175    }
176}
177
178impl<Db: ?Sized, A, B, C> DebugWithDb<Db> for (A, B, C)
179where
180    A: DebugWithDb<Db>,
181    B: DebugWithDb<Db>,
182    C: DebugWithDb<Db>,
183{
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
185        f.debug_tuple("")
186            .field(&self.0.debug(db))
187            .field(&self.1.debug(db))
188            .field(&self.2.debug(db))
189            .finish()
190    }
191}
192
193impl<Db: ?Sized, V, S> DebugWithDb<Db> for HashSet<V, S>
194where
195    V: DebugWithDb<Db>,
196{
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
198        let elements = self.iter().map(|e| e.debug(db));
199        f.debug_list().entries(elements).finish()
200    }
201}
202
203impl<Db: ?Sized, V> DebugWithDb<Db> for BTreeSet<V>
204where
205    V: DebugWithDb<Db>,
206{
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
208        let elements = self.iter().map(|e| e.debug(db));
209        f.debug_list().entries(elements).finish()
210    }
211}
212
213impl<Db: ?Sized, V: Hash + Eq> DebugWithDb<Db> for OrderedHashSet<V>
214where
215    V: DebugWithDb<Db>,
216{
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
218        let elements = self.iter().map(|e| e.debug(db));
219        f.debug_list().entries(elements).finish()
220    }
221}
222
223/// This is used by the macro generated code.
224/// If the field type implements `DebugWithDb`, uses that, otherwise, uses `Debug`.
225/// That's the "has impl" trick <https://github.com/nvzqz/impls#how-it-works>
226#[doc(hidden)]
227pub mod helper {
228    use std::fmt;
229    use std::marker::PhantomData;
230
231    use super::{DebugWith, DebugWithDb};
232
233    pub trait Fallback<T: fmt::Debug, Db: ?Sized> {
234        fn helper_debug<'a>(a: &'a T, _db: &Db) -> &'a dyn fmt::Debug {
235            a
236        }
237    }
238
239    pub struct HelperDebug<T, Db: ?Sized>(PhantomData<T>, PhantomData<Db>);
240
241    impl<T: DebugWithDb<Db>, Db: ?Sized> HelperDebug<T, Db> {
242        #[allow(dead_code)]
243        pub fn helper_debug<'a, 'b: 'a>(a: &'a T, db: &'b Db) -> DebugWith<'a, Db> {
244            a.debug(db)
245        }
246    }
247
248    impl<Everything, Db: ?Sized, T: fmt::Debug> Fallback<T, Db> for Everything {}
249}