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::rc::Rc;
7use std::sync::Arc;
8
9use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
10use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
11
12pub trait DebugWithDb<'db> {
13    type Db: ?Sized;
14
15    fn debug<'me>(&'me self, db: &'db Self::Db) -> DebugWith<'me, 'db, Self::Db>
16    where
17        Self: Sized + 'me,
18    {
19        DebugWith { value: BoxRef::Ref(self), db }
20    }
21
22    fn into_debug<'me>(self, db: &'db Self::Db) -> DebugWith<'me, 'db, Self::Db>
23    where
24        Self: Sized + 'me,
25    {
26        DebugWith { value: BoxRef::Box(Box::new(self)), db }
27    }
28
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result;
30}
31
32/// A trait that allows upcasting the database to the type T.
33pub trait DebugDbUpcast<'db, T: ?Sized> {
34    fn debug_db_upcast(&'db self) -> &'db T;
35}
36impl<'db, T: ?Sized> DebugDbUpcast<'db, T> for T {
37    fn debug_db_upcast(&'db self) -> &'db T {
38        self
39    }
40}
41
42pub struct DebugWith<'me, 'db, Db: ?Sized> {
43    value: BoxRef<'me, dyn DebugWithDb<'db, Db = Db> + 'me>,
44    db: &'db Db,
45}
46
47enum BoxRef<'me, T: ?Sized> {
48    Box(Box<T>),
49    Ref(&'me T),
50}
51
52impl<T: ?Sized> std::ops::Deref for BoxRef<'_, T> {
53    type Target = T;
54
55    fn deref(&self) -> &Self::Target {
56        match self {
57            BoxRef::Box(b) => b,
58            BoxRef::Ref(r) => r,
59        }
60    }
61}
62
63impl<D: ?Sized> std::fmt::Debug for DebugWith<'_, '_, D> {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        DebugWithDb::fmt(&*self.value, f, self.db)
66    }
67}
68
69impl<'db, T: ?Sized> DebugWithDb<'db> for &T
70where
71    T: DebugWithDb<'db>,
72{
73    type Db = T::Db;
74
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
76        T::fmt(self, f, db)
77    }
78}
79
80impl<'db, T: ?Sized> DebugWithDb<'db> for Box<T>
81where
82    T: DebugWithDb<'db>,
83{
84    type Db = T::Db;
85
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
87        T::fmt(self, f, db)
88    }
89}
90
91impl<'db, T> DebugWithDb<'db> for Rc<T>
92where
93    T: DebugWithDb<'db>,
94{
95    type Db = T::Db;
96
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
98        T::fmt(self, f, db)
99    }
100}
101
102impl<'db, T: ?Sized> DebugWithDb<'db> for Arc<T>
103where
104    T: DebugWithDb<'db>,
105{
106    type Db = T::Db;
107
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
109        T::fmt(self, f, db)
110    }
111}
112
113impl<'db, T> DebugWithDb<'db> for [T]
114where
115    T: DebugWithDb<'db>,
116{
117    type Db = T::Db;
118
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
120        let elements = self.iter().map(|e| e.debug(db));
121        f.debug_list().entries(elements).finish()
122    }
123}
124
125impl<'db, T> DebugWithDb<'db> for Vec<T>
126where
127    T: DebugWithDb<'db>,
128{
129    type Db = T::Db;
130
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
132        let elements = self.iter().map(|e| e.debug(db));
133        f.debug_list().entries(elements).finish()
134    }
135}
136
137impl<'db, T> DebugWithDb<'db> for Option<T>
138where
139    T: DebugWithDb<'db>,
140{
141    type Db = T::Db;
142
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
144        let me = self.as_ref().map(|v| v.debug(db));
145        std::fmt::Debug::fmt(&me, f)
146    }
147}
148
149#[expect(clippy::disallowed_types)]
150impl<'db, K, V, S> DebugWithDb<'db> for std::collections::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 std::collections::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
231#[expect(clippy::disallowed_types)]
232impl<'db, V, S> DebugWithDb<'db> for std::collections::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 std::collections::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> 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}