Skip to main content

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::rc::Rc;
8use std::sync::Arc;
9
10use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
11use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
12
13pub trait DebugWithDb<'db> {
14    type Db: ?Sized;
15
16    fn debug<'me>(&'me self, db: &'db Self::Db) -> DebugWith<'me, 'db, Self::Db>
17    where
18        Self: Sized + 'me,
19    {
20        DebugWith { value: BoxRef::Ref(self), db }
21    }
22
23    fn into_debug<'me>(self, db: &'db Self::Db) -> DebugWith<'me, 'db, Self::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 Self::Db) -> std::fmt::Result;
31}
32
33/// A trait that allows upcasting the database to the type T.
34pub trait DebugDbUpcast<'db, T: ?Sized> {
35    fn debug_db_upcast(&'db self) -> &'db T;
36}
37impl<'db, T: ?Sized> DebugDbUpcast<'db, T> for T {
38    fn debug_db_upcast(&'db self) -> &'db T {
39        self
40    }
41}
42
43pub struct DebugWith<'me, 'db, Db: ?Sized> {
44    value: BoxRef<'me, dyn DebugWithDb<'db, Db = Db> + 'me>,
45    db: &'db Db,
46}
47
48enum BoxRef<'me, T: ?Sized> {
49    Box(Box<T>),
50    Ref(&'me T),
51}
52
53impl<T: ?Sized> std::ops::Deref for BoxRef<'_, T> {
54    type Target = T;
55
56    fn deref(&self) -> &Self::Target {
57        match self {
58            BoxRef::Box(b) => b,
59            BoxRef::Ref(r) => r,
60        }
61    }
62}
63
64impl<D: ?Sized> std::fmt::Debug for DebugWith<'_, '_, D> {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        DebugWithDb::fmt(&*self.value, f, self.db)
67    }
68}
69
70impl<'db, T: ?Sized> DebugWithDb<'db> for &T
71where
72    T: DebugWithDb<'db>,
73{
74    type Db = T::Db;
75
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
77        T::fmt(self, f, db)
78    }
79}
80
81impl<'db, T: ?Sized> DebugWithDb<'db> for Box<T>
82where
83    T: DebugWithDb<'db>,
84{
85    type Db = T::Db;
86
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
88        T::fmt(self, f, db)
89    }
90}
91
92impl<'db, T> DebugWithDb<'db> for Rc<T>
93where
94    T: DebugWithDb<'db>,
95{
96    type Db = T::Db;
97
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
99        T::fmt(self, f, db)
100    }
101}
102
103impl<'db, T: ?Sized> DebugWithDb<'db> for Arc<T>
104where
105    T: DebugWithDb<'db>,
106{
107    type Db = T::Db;
108
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
110        T::fmt(self, f, db)
111    }
112}
113
114impl<'db, T> DebugWithDb<'db> for [T]
115where
116    T: DebugWithDb<'db>,
117{
118    type Db = T::Db;
119
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
121        let elements = self.iter().map(|e| e.debug(db));
122        f.debug_list().entries(elements).finish()
123    }
124}
125
126impl<'db, T> DebugWithDb<'db> for Vec<T>
127where
128    T: DebugWithDb<'db>,
129{
130    type Db = T::Db;
131
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
133        let elements = self.iter().map(|e| e.debug(db));
134        f.debug_list().entries(elements).finish()
135    }
136}
137
138impl<'db, T> DebugWithDb<'db> for Option<T>
139where
140    T: DebugWithDb<'db>,
141{
142    type Db = T::Db;
143
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
145        let me = self.as_ref().map(|v| v.debug(db));
146        std::fmt::Debug::fmt(&me, f)
147    }
148}
149
150impl<'db, K, V, S> DebugWithDb<'db> for HashMap<K, V, S>
151where
152    K: DebugWithDb<'db>,
153    V: DebugWithDb<'db, Db = K::Db>,
154{
155    type Db = K::Db;
156
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
158        let elements = self.iter().map(|(k, v)| (k.debug(db), v.debug(db)));
159        f.debug_map().entries(elements).finish()
160    }
161}
162
163impl<'db, K, V> DebugWithDb<'db> for BTreeMap<K, V>
164where
165    K: DebugWithDb<'db>,
166    V: DebugWithDb<'db, Db = K::Db>,
167{
168    type Db = K::Db;
169
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
171        let elements = self.iter().map(|(k, v)| (k.debug(db), v.debug(db)));
172        f.debug_map().entries(elements).finish()
173    }
174}
175
176impl<'db, K, V> DebugWithDb<'db> for OrderedHashMap<K, V>
177where
178    K: DebugWithDb<'db>,
179    V: DebugWithDb<'db, Db = K::Db>,
180{
181    type Db = K::Db;
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
183        let elements = self.iter().map(|(k, v)| (k.debug(db), v.debug(db)));
184        f.debug_map().entries(elements).finish()
185    }
186}
187
188impl<'db, A> DebugWithDb<'db> for (A,)
189where
190    A: DebugWithDb<'db>,
191{
192    type Db = A::Db;
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
194        f.debug_tuple("").field(&self.0.debug(db)).finish()
195    }
196}
197
198impl<'db, A, B> DebugWithDb<'db> for (A, B)
199where
200    A: DebugWithDb<'db>,
201    B: DebugWithDb<'db> + 'db,
202    A::Db: DebugDbUpcast<'db, B::Db>,
203{
204    type Db = A::Db;
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
206        f.debug_tuple("")
207            .field(&self.0.debug(db))
208            .field(&self.1.debug(db.debug_db_upcast()))
209            .finish()
210    }
211}
212
213impl<'db, A, B, C> DebugWithDb<'db> for (A, B, C)
214where
215    A: DebugWithDb<'db>,
216    B: DebugWithDb<'db> + 'db,
217    C: DebugWithDb<'db> + 'db,
218    A::Db: DebugDbUpcast<'db, B::Db>,
219    A::Db: DebugDbUpcast<'db, C::Db>,
220{
221    type Db = A::Db;
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
223        f.debug_tuple("")
224            .field(&self.0.debug(db))
225            .field(&self.1.debug(db.debug_db_upcast()))
226            .field(&self.2.debug(db.debug_db_upcast()))
227            .finish()
228    }
229}
230
231impl<'db, V, S> DebugWithDb<'db> for HashSet<V, S>
232where
233    V: DebugWithDb<'db>,
234{
235    type Db = V::Db;
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
237        let elements = self.iter().map(|e| e.debug(db));
238        f.debug_list().entries(elements).finish()
239    }
240}
241
242impl<'db, V> DebugWithDb<'db> for BTreeSet<V>
243where
244    V: DebugWithDb<'db>,
245{
246    type Db = V::Db;
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
248        let elements = self.iter().map(|e| e.debug(db));
249        f.debug_list().entries(elements).finish()
250    }
251}
252
253impl<'db, V> DebugWithDb<'db> for OrderedHashSet<V>
254where
255    V: DebugWithDb<'db>,
256{
257    type Db = V::Db;
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
259        let elements = self.iter().map(|e| e.debug(db));
260        f.debug_list().entries(elements).finish()
261    }
262}
263
264/// A flexible debug trait that takes the database as a type parameter
265/// instead of an associated type, allowing multiple implementations
266/// for the same type with different database types.
267pub trait DebugWithDbOverride<'db, Db: ?Sized> {
268    fn fmt_override(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Db) -> std::fmt::Result;
269}
270
271impl<'db, T> DebugWithDb<'db> for id_arena::Id<T>
272where
273    T: DebugWithDb<'db>,
274    id_arena::Id<T>: DebugWithDbOverride<'db, T::Db>,
275{
276    type Db = T::Db;
277
278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
279        self.fmt_override(f, db)
280    }
281}
282
283/// This is used by the macro generated code.
284/// If the field type implements `DebugWithDb`, uses that, otherwise, uses `Debug`.
285/// That's the "has impl" trick <https://github.com/nvzqz/impls#how-it-works>
286#[doc(hidden)]
287pub mod helper {
288    use std::fmt;
289    use std::marker::PhantomData;
290
291    use super::{DebugDbUpcast, DebugWith, DebugWithDb};
292
293    pub trait Fallback<'db, T: fmt::Debug, Db: ?Sized> {
294        fn helper_debug(a: &'db T, _db: &'db Db) -> &'db dyn fmt::Debug {
295            a
296        }
297    }
298
299    pub struct HelperDebug<T, Db: ?Sized>(PhantomData<T>, PhantomData<Db>);
300
301    impl<'db, T: DebugWithDb<'db>, Db: ?Sized + DebugDbUpcast<'db, T::Db>> HelperDebug<T, Db> {
302        #[allow(dead_code)]
303        pub fn helper_debug<'me>(a: &'me T, db: &'db Db) -> DebugWith<'me, 'db, T::Db> {
304            a.debug(db.debug_db_upcast())
305        }
306    }
307
308    impl<'db, Everything, T: fmt::Debug, Db: ?Sized> Fallback<'db, T, Db> for Everything {}
309}