turbo_tasks/debug/
mod.rs

1use std::fmt::{Debug, Display};
2
3use auto_hash_map::{AutoMap, AutoSet};
4use smallvec::SmallVec;
5use turbo_rcstr::RcStr;
6use turbo_tasks::{FxIndexMap, FxIndexSet, Vc};
7pub use turbo_tasks_macros::ValueDebugFormat;
8
9use crate::{self as turbo_tasks};
10
11#[doc(hidden)]
12pub mod internal;
13mod vdbg;
14
15use internal::PassthroughDebug;
16
17/// The return type of [`ValueDebug::dbg`].
18///
19/// We don't use [`Vc<RcStr>`][turbo_rcstr::RcStr] or [`String`] directly because we
20/// don't want the [`Debug`]/[`Display`] representations to be escaped.
21#[turbo_tasks::value]
22pub struct ValueDebugString(String);
23
24impl Debug for ValueDebugString {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        f.write_str(&self.0)
27    }
28}
29
30impl Display for ValueDebugString {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        f.write_str(&self.0)
33    }
34}
35
36impl ValueDebugString {
37    /// Returns the underlying string.
38    pub fn as_str(&self) -> &str {
39        &self.0
40    }
41}
42
43impl ValueDebugString {
44    /// Create a new `ValueDebugString` from a string.
45    pub fn new(s: String) -> Vc<Self> {
46        ValueDebugString::cell(ValueDebugString(s))
47    }
48}
49
50/// [`Debug`]-like trait for [`Vc`] types, automatically derived when using
51/// [`macro@turbo_tasks::value`] and [`turbo_tasks::value_trait`].
52///
53/// # Usage
54///
55/// ```ignore
56/// dbg!(any_vc.dbg().await?);
57/// ```
58#[turbo_tasks::value_trait(no_debug)]
59pub trait ValueDebug {
60    #[turbo_tasks::function]
61    fn dbg(self: Vc<Self>) -> Vc<ValueDebugString>;
62
63    /// Like `dbg`, but with a depth limit.
64    #[turbo_tasks::function]
65    fn dbg_depth(self: Vc<Self>, depth: usize) -> Vc<ValueDebugString>;
66}
67
68/// Use [autoref specialization] to implement [`ValueDebug`] for `T: Debug`.
69///
70/// [autoref specialization]: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md
71pub trait ValueDebugFormat {
72    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_>;
73}
74
75impl ValueDebugFormat for String {
76    fn value_debug_format(&self, _depth: usize) -> ValueDebugFormatString<'_> {
77        ValueDebugFormatString::Sync(format!("{self:#?}"))
78    }
79}
80
81impl ValueDebugFormat for RcStr {
82    fn value_debug_format(&self, _: usize) -> ValueDebugFormatString<'_> {
83        ValueDebugFormatString::Sync(self.to_string())
84    }
85}
86
87// Use autoref specialization [1] to implement `ValueDebugFormat` for `T:
88// Debug` as a fallback if `T` does not implement it directly, hence the `for
89// &T` clause.
90//
91// [1] https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md
92impl<T> ValueDebugFormat for &T
93where
94    T: Debug,
95{
96    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
97        if depth == 0 {
98            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
99        }
100
101        ValueDebugFormatString::Sync(format!("{self:#?}"))
102    }
103}
104
105impl<T> ValueDebugFormat for Option<T>
106where
107    T: ValueDebugFormat,
108{
109    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
110        if depth == 0 {
111            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
112        }
113
114        match self {
115            None => ValueDebugFormatString::Sync(format!("{:#?}", Option::<()>::None)),
116            Some(value) => match value.value_debug_format(depth.saturating_sub(1)) {
117                ValueDebugFormatString::Sync(string) => ValueDebugFormatString::Sync(format!(
118                    "{:#?}",
119                    Some(PassthroughDebug::new_string(string))
120                )),
121                ValueDebugFormatString::Async(future) => {
122                    ValueDebugFormatString::Async(Box::pin(async move {
123                        let string = future.await?;
124                        Ok(format!("{:#?}", Some(PassthroughDebug::new_string(string))))
125                    }))
126                }
127            },
128        }
129    }
130}
131
132impl<T> ValueDebugFormat for Vec<T>
133where
134    T: ValueDebugFormat,
135{
136    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
137        if depth == 0 {
138            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
139        }
140
141        let values = self
142            .iter()
143            .map(|value| value.value_debug_format(depth.saturating_sub(1)))
144            .collect::<Vec<_>>();
145
146        ValueDebugFormatString::Async(Box::pin(async move {
147            let mut values_string = vec![];
148            for value in values {
149                match value {
150                    ValueDebugFormatString::Sync(string) => {
151                        values_string.push(PassthroughDebug::new_string(string));
152                    }
153                    ValueDebugFormatString::Async(future) => {
154                        values_string.push(PassthroughDebug::new_string(future.await?));
155                    }
156                }
157            }
158            Ok(format!("{values_string:#?}"))
159        }))
160    }
161}
162
163impl<T, const N: usize> ValueDebugFormat for SmallVec<[T; N]>
164where
165    T: ValueDebugFormat,
166{
167    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
168        if depth == 0 {
169            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
170        }
171
172        let values = self
173            .iter()
174            .map(|value| value.value_debug_format(depth.saturating_sub(1)))
175            .collect::<Vec<_>>();
176
177        ValueDebugFormatString::Async(Box::pin(async move {
178            let mut values_string = vec![];
179            for value in values {
180                match value {
181                    ValueDebugFormatString::Sync(string) => {
182                        values_string.push(PassthroughDebug::new_string(string));
183                    }
184                    ValueDebugFormatString::Async(future) => {
185                        values_string.push(PassthroughDebug::new_string(future.await?));
186                    }
187                }
188            }
189            Ok(format!("{values_string:#?}"))
190        }))
191    }
192}
193
194impl<K> ValueDebugFormat for AutoSet<K>
195where
196    K: ValueDebugFormat,
197{
198    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
199        if depth == 0 {
200            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
201        }
202
203        let values = self
204            .iter()
205            .map(|item| item.value_debug_format(depth.saturating_sub(1)))
206            .collect::<Vec<_>>();
207
208        ValueDebugFormatString::Async(Box::pin(async move {
209            let mut values_string = Vec::with_capacity(values.len());
210            for item in values {
211                match item {
212                    ValueDebugFormatString::Sync(string) => {
213                        values_string.push(PassthroughDebug::new_string(string));
214                    }
215                    ValueDebugFormatString::Async(future) => {
216                        values_string.push(PassthroughDebug::new_string(future.await?));
217                    }
218                }
219            }
220            Ok(format!("{values_string:#?}"))
221        }))
222    }
223}
224
225impl<K, V, S> ValueDebugFormat for std::collections::HashMap<K, V, S>
226where
227    K: Debug,
228    V: ValueDebugFormat,
229{
230    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
231        if depth == 0 {
232            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
233        }
234
235        let values = self
236            .iter()
237            .map(|(key, value)| {
238                (
239                    format!("{key:#?}"),
240                    value.value_debug_format(depth.saturating_sub(1)),
241                )
242            })
243            .collect::<Vec<_>>();
244
245        ValueDebugFormatString::Async(Box::pin(async move {
246            let mut values_string = std::collections::HashMap::new();
247            for (key, value) in values {
248                match value {
249                    ValueDebugFormatString::Sync(string) => {
250                        values_string.insert(key, PassthroughDebug::new_string(string));
251                    }
252                    ValueDebugFormatString::Async(future) => {
253                        values_string.insert(key, PassthroughDebug::new_string(future.await?));
254                    }
255                }
256            }
257            Ok(format!("{values_string:#?}"))
258        }))
259    }
260}
261
262impl<K, V> ValueDebugFormat for AutoMap<K, V>
263where
264    K: Debug,
265    V: ValueDebugFormat,
266{
267    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
268        if depth == 0 {
269            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
270        }
271
272        let values = self
273            .iter()
274            .map(|(key, value)| {
275                (
276                    format!("{key:#?}"),
277                    value.value_debug_format(depth.saturating_sub(1)),
278                )
279            })
280            .collect::<Vec<_>>();
281
282        ValueDebugFormatString::Async(Box::pin(async move {
283            let mut values_string = AutoMap::new();
284            for (key, value) in values {
285                match value {
286                    ValueDebugFormatString::Sync(string) => {
287                        values_string.insert(key, PassthroughDebug::new_string(string));
288                    }
289                    ValueDebugFormatString::Async(future) => {
290                        values_string.insert(key, PassthroughDebug::new_string(future.await?));
291                    }
292                }
293            }
294            Ok(format!("{values_string:#?}"))
295        }))
296    }
297}
298
299impl<T> ValueDebugFormat for FxIndexSet<T>
300where
301    T: ValueDebugFormat,
302{
303    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
304        if depth == 0 {
305            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
306        }
307
308        let values = self
309            .iter()
310            .map(|value| value.value_debug_format(depth.saturating_sub(1)))
311            .collect::<Vec<_>>();
312
313        ValueDebugFormatString::Async(Box::pin(async move {
314            let mut values_string = FxIndexSet::default();
315            for value in values {
316                let value = match value {
317                    ValueDebugFormatString::Sync(string) => string,
318                    ValueDebugFormatString::Async(future) => future.await?,
319                };
320                values_string.insert(PassthroughDebug::new_string(value));
321            }
322            Ok(format!("{values_string:#?}"))
323        }))
324    }
325}
326
327impl<K, V> ValueDebugFormat for FxIndexMap<K, V>
328where
329    K: ValueDebugFormat,
330    V: ValueDebugFormat,
331{
332    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
333        if depth == 0 {
334            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
335        }
336
337        let values = self
338            .iter()
339            .map(|(key, value)| {
340                (
341                    key.value_debug_format(depth.saturating_sub(1)),
342                    value.value_debug_format(depth.saturating_sub(1)),
343                )
344            })
345            .collect::<Vec<_>>();
346
347        ValueDebugFormatString::Async(Box::pin(async move {
348            let mut values_string = FxIndexMap::default();
349            for (key, value) in values {
350                let key = match key {
351                    ValueDebugFormatString::Sync(string) => string,
352                    ValueDebugFormatString::Async(future) => future.await?,
353                };
354                let value = match value {
355                    ValueDebugFormatString::Sync(string) => string,
356                    ValueDebugFormatString::Async(future) => future.await?,
357                };
358                values_string.insert(
359                    PassthroughDebug::new_string(key),
360                    PassthroughDebug::new_string(value),
361                );
362            }
363            Ok(format!("{values_string:#?}"))
364        }))
365    }
366}
367
368macro_rules! tuple_impls {
369    ( $( $name:ident )+ ) => {
370        impl<$($name: ValueDebugFormat),+> ValueDebugFormat for ($($name,)+)
371        {
372            #[allow(non_snake_case)]
373            fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
374                if depth == 0 {
375                    return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
376                }
377
378                let ($($name,)+) = self;
379                let ($($name,)+) = ($($name.value_debug_format(depth.saturating_sub(1)),)+);
380
381                ValueDebugFormatString::Async(Box::pin(async move {
382                    let values = ($(PassthroughDebug::new_string($name.try_to_string().await?),)+);
383                    Ok(format!("{:#?}", values))
384                }))
385            }
386        }
387    };
388}
389
390tuple_impls! { A }
391tuple_impls! { A B }
392tuple_impls! { A B C }
393tuple_impls! { A B C D }
394tuple_impls! { A B C D E }
395tuple_impls! { A B C D E F }
396tuple_impls! { A B C D E F G }
397tuple_impls! { A B C D E F G H }
398tuple_impls! { A B C D E F G H I }
399tuple_impls! { A B C D E F G H I J }
400tuple_impls! { A B C D E F G H I J K }
401tuple_impls! { A B C D E F G H I J K L }
402
403/// Output of `ValueDebugFormat::value_debug_format`.
404pub enum ValueDebugFormatString<'a> {
405    /// For the `T: Debug` fallback implementation, we can output a string
406    /// directly as the result of `format!("{:?}", t)`.
407    Sync(String),
408    /// For the `Vc` types and `Vc`-containing types implementations, we need to
409    /// resolve types asynchronously before we can format them, hence the need
410    /// for a future.
411    Async(
412        core::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<String>> + Send + 'a>>,
413    ),
414}
415
416impl ValueDebugFormatString<'_> {
417    /// Convert the `ValueDebugFormatString` into a `String`.
418    ///
419    /// This can fail when resolving `Vc` types.
420    pub async fn try_to_string(self) -> anyhow::Result<String> {
421        Ok(match self {
422            ValueDebugFormatString::Sync(value) => value,
423            ValueDebugFormatString::Async(future) => future.await?,
424        })
425    }
426
427    /// Convert the `ValueDebugFormatString` into a `Vc<ValueDebugString>`.
428    ///
429    /// This can fail when resolving `Vc` types.
430    pub async fn try_to_value_debug_string(self) -> anyhow::Result<Vc<ValueDebugString>> {
431        Ok(ValueDebugString::new(self.try_to_string().await?))
432    }
433}