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