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