Skip to main content

bevy_reflect/
display.rs

1//! Code for displaying reflected values in a consistent, human-readable format.
2//!
3//! Primarily useful for interactive workflows,
4//! such as inspecting reflected values in a text-based format,
5//! or logging them to the console.
6//! Because the output is pure text, it is particularly useful for automated inspection,
7//! println!-debugging, and as output for screen readers.
8//!
9//! Be mindful: these [`Display`] implementations return the full type path for each contained type.
10//! While useful for detailed inspection, it can be unhelpfully verbose, especially for nested values.
11//! To recursively collapse all contained type paths to their "short names" (i.e. with no crate/module paths),
12//! use [`disqualified::ShortName::from`] on the returned [`String`](alloc::string::String),
13//! then use the [`Display`] implementation on [`ShortName`](disqualified::ShortName) for more concise output.
14//!
15//! These implementations are stored in their own module rather than beside the types themselves
16//! to help ensure consistency and reduce clutter.
17//!
18//! Note that these implementations are for the trait objects (`dyn Struct`, etc.) rather than for every `T: Struct`.
19//! This is deliberate: it reduces the compile time and binary costs
20//! associated with generating this code for every type that implements `Reflect`.
21//! To use them with a concrete type, cast your value to a trait object before formatting it:
22//! e.g. `format!("{}", &my_value as &dyn Reflect)`.
23
24use alloc::vec::Vec;
25use core::any::TypeId;
26use core::fmt::{Display, Formatter, Write};
27
28#[cfg(feature = "functions")]
29use crate::func::Function;
30use crate::{
31    array::Array,
32    enums::{Enum, VariantType},
33    list::List,
34    map::Map,
35    set::Set,
36    structs::Struct,
37    tuple::Tuple,
38    tuple_struct::TupleStruct,
39    PartialReflect, Reflect, ReflectRef, TypeInfo,
40};
41
42/// String-formats a reflected value, detecting cycles via their [`TypeId`].
43///
44/// If `value`'s [`TypeId`] already appears in `ancestry`, the output is
45/// truncated to `{type_path} { ... }` to avoid infinite recursion.
46///
47/// This catches both direct recursion (a type containing itself)
48/// and mutual recursion (type A containing type B containing type A).
49///
50/// Dynamic types without type info cannot be cycle-checked.
51///
52/// `indent` is the indentation level at which this value's closing delimiter
53/// (or last continuation line for opaque values) should appear.
54fn write_value(
55    f: &mut Formatter<'_>,
56    value: &dyn PartialReflect,
57    // A HashSet / BTreeSet has better asymptotic performance for the .contains() check,
58    // but a Vec will be faster at low n, which is the common case for reflected values,
59    // as most types are not deeply nested.
60    ancestry: &mut Vec<TypeId>,
61    indent: u32,
62) -> core::fmt::Result {
63    // Dynamic types without type info cannot be cycle-checked.
64    let Some(type_info) = value.get_represented_type_info() else {
65        return write_reflect_ref(f, &value.reflect_ref(), ancestry, indent);
66    };
67
68    let type_id = type_info.type_id();
69    let type_path = type_info.type_path();
70
71    if ancestry.contains(&type_id) {
72        return write!(f, "{type_path} {{ ... }}");
73    }
74
75    ancestry.push(type_id);
76    let result = write_reflect_ref(f, &value.reflect_ref(), ancestry, indent);
77    ancestry.pop();
78    result
79}
80
81/// Writes a [`ReflectRef`], dispatching to the type-specific writer.
82fn write_reflect_ref(
83    f: &mut Formatter<'_>,
84    reflect_ref: &ReflectRef<'_>,
85    ancestry: &mut Vec<TypeId>,
86    indent: u32,
87) -> core::fmt::Result {
88    match *reflect_ref {
89        ReflectRef::Struct(value) => write_struct(f, value, ancestry, indent),
90        ReflectRef::TupleStruct(value) => write_tuple_struct(f, value, ancestry, indent),
91        ReflectRef::Tuple(value) => write_tuple(f, value, ancestry, indent),
92        ReflectRef::List(value) => write_list(f, value, ancestry, indent),
93        ReflectRef::Array(value) => write_array(f, value, ancestry, indent),
94        ReflectRef::Map(value) => write_map(f, value, ancestry, indent),
95        ReflectRef::Set(value) => write_set(f, value, ancestry, indent),
96        ReflectRef::Enum(value) => write_enum(f, value, ancestry, indent),
97        ReflectRef::Opaque(value) => write_opaque(f, value, indent),
98        #[cfg(feature = "functions")]
99        ReflectRef::Function(function) => write_function(f, function),
100    }
101}
102
103/// Writes a reflected [`Struct`] value.
104fn write_struct(
105    f: &mut Formatter<'_>,
106    value: &dyn Struct,
107    ancestry: &mut Vec<TypeId>,
108    indent: u32,
109) -> core::fmt::Result {
110    let type_name = display_type_name(value.get_represented_type_info(), "<Unknown Struct>");
111
112    if value.field_len() == 0 {
113        return write!(f, "{type_name} {{}}");
114    }
115
116    write_delimited_block(f, type_name, " ", '{', '}', indent, |fmt, entry_indent| {
117        for i in 0..value.field_len() {
118            let field_name = value.name_at(i).unwrap_or("<Unknown Field>");
119            write_indent(fmt, entry_indent)?;
120            fmt.write_str(field_name)?;
121            fmt.write_str(": ")?;
122            match value.field_at(i) {
123                Some(child) => write_value(fmt, child, ancestry, entry_indent)?,
124                None => fmt.write_str("<None>")?,
125            }
126            fmt.write_str(",\n")?;
127        }
128        Ok(())
129    })
130}
131
132/// Writes a reflected [`TupleStruct`] value.
133fn write_tuple_struct(
134    f: &mut Formatter<'_>,
135    value: &dyn TupleStruct,
136    ancestry: &mut Vec<TypeId>,
137    indent: u32,
138) -> core::fmt::Result {
139    let type_name = display_type_name(value.get_represented_type_info(), "<Unknown TupleStruct>");
140
141    if value.field_len() == 0 {
142        return write!(f, "{type_name}()");
143    }
144
145    write_delimited_block(f, type_name, "", '(', ')', indent, |fmt, entry_indent| {
146        for i in 0..value.field_len() {
147            write_indent(fmt, entry_indent)?;
148            match value.field(i) {
149                Some(child) => write_value(fmt, child, ancestry, entry_indent)?,
150                None => fmt.write_str("<None>")?,
151            }
152            fmt.write_str(",\n")?;
153        }
154        Ok(())
155    })
156}
157
158/// Writes a reflected [`Tuple`] value.
159fn write_tuple(
160    f: &mut Formatter<'_>,
161    value: &dyn Tuple,
162    ancestry: &mut Vec<TypeId>,
163    indent: u32,
164) -> core::fmt::Result {
165    if value.field_len() == 0 {
166        return f.write_str("()");
167    }
168
169    write_delimited_block(f, "", "", '(', ')', indent, |fmt, entry_indent| {
170        for i in 0..value.field_len() {
171            write_indent(fmt, entry_indent)?;
172            match value.field(i) {
173                Some(child) => write_value(fmt, child, ancestry, entry_indent)?,
174                None => fmt.write_str("<None>")?,
175            }
176            fmt.write_str(",\n")?;
177        }
178        Ok(())
179    })
180}
181
182/// Writes a reflected [`List`] value.
183fn write_list(
184    f: &mut Formatter<'_>,
185    value: &dyn List,
186    ancestry: &mut Vec<TypeId>,
187    indent: u32,
188) -> core::fmt::Result {
189    if value.is_empty() {
190        return f.write_str("[]");
191    }
192
193    write_delimited_block(f, "", "", '[', ']', indent, |fmt, entry_indent| {
194        for i in 0..value.len() {
195            write_indent(fmt, entry_indent)?;
196            match value.get(i) {
197                Some(child) => write_value(fmt, child, ancestry, entry_indent)?,
198                None => fmt.write_str("<None>")?,
199            }
200            fmt.write_str(",\n")?;
201        }
202        Ok(())
203    })
204}
205
206/// Writes a reflected [`Array`] value.
207fn write_array(
208    f: &mut Formatter<'_>,
209    value: &dyn Array,
210    ancestry: &mut Vec<TypeId>,
211    indent: u32,
212) -> core::fmt::Result {
213    if value.is_empty() {
214        return f.write_str("[]");
215    }
216
217    write_delimited_block(f, "", "", '[', ']', indent, |fmt, entry_indent| {
218        for i in 0..value.len() {
219            write_indent(fmt, entry_indent)?;
220            match value.get(i) {
221                Some(child) => write_value(fmt, child, ancestry, entry_indent)?,
222                None => fmt.write_str("<None>")?,
223            }
224            fmt.write_str(",\n")?;
225        }
226        Ok(())
227    })
228}
229
230/// Writes a reflected [`Map`] value.
231fn write_map(
232    f: &mut Formatter<'_>,
233    value: &dyn Map,
234    ancestry: &mut Vec<TypeId>,
235    indent: u32,
236) -> core::fmt::Result {
237    if value.is_empty() {
238        return f.write_str("{}");
239    }
240
241    write_delimited_block(f, "", "", '{', '}', indent, |fmt, entry_indent| {
242        for (key, key_value) in value.iter() {
243            write_indent(fmt, entry_indent)?;
244            write_value(fmt, key, ancestry, entry_indent)?;
245            fmt.write_str(": ")?;
246            write_value(fmt, key_value, ancestry, entry_indent)?;
247            fmt.write_str(",\n")?;
248        }
249        Ok(())
250    })
251}
252
253/// Writes a reflected [`Set`] value.
254fn write_set(
255    f: &mut Formatter<'_>,
256    value: &dyn Set,
257    ancestry: &mut Vec<TypeId>,
258    indent: u32,
259) -> core::fmt::Result {
260    if value.is_empty() {
261        return f.write_str("{}");
262    }
263
264    write_delimited_block(f, "", "", '{', '}', indent, |fmt, entry_indent| {
265        for element in value.iter() {
266            write_indent(fmt, entry_indent)?;
267            write_value(fmt, element, ancestry, entry_indent)?;
268            fmt.write_str(",\n")?;
269        }
270        Ok(())
271    })
272}
273
274/// Writes a reflected [`Enum`] value.
275fn write_enum(
276    f: &mut Formatter<'_>,
277    value: &dyn Enum,
278    ancestry: &mut Vec<TypeId>,
279    indent: u32,
280) -> core::fmt::Result {
281    let type_name = display_type_name(value.get_represented_type_info(), "<Unknown Enum>");
282    let variant = value.variant_name();
283    let variant = if variant.is_empty() {
284        "<unnamed>"
285    } else {
286        variant
287    };
288    let qualified = alloc::format!("{type_name}::{variant}");
289
290    match value.variant_type() {
291        VariantType::Struct => {
292            if value.field_len() == 0 {
293                return write!(f, "{qualified} {{}}");
294            }
295
296            write_delimited_block(f, &qualified, " ", '{', '}', indent, |fmt, entry_indent| {
297                for i in 0..value.field_len() {
298                    let field_name = value.name_at(i).unwrap_or("<Unknown Field>");
299                    write_indent(fmt, entry_indent)?;
300                    fmt.write_str(field_name)?;
301                    fmt.write_str(": ")?;
302                    match value.field_at(i) {
303                        Some(child) => write_value(fmt, child, ancestry, entry_indent)?,
304                        None => fmt.write_str("<None>")?,
305                    }
306                    fmt.write_str(",\n")?;
307                }
308                Ok(())
309            })
310        }
311        VariantType::Tuple => {
312            if value.field_len() == 0 {
313                return write!(f, "{qualified}()");
314            }
315
316            write_delimited_block(f, &qualified, "", '(', ')', indent, |fmt, entry_indent| {
317                for i in 0..value.field_len() {
318                    write_indent(fmt, entry_indent)?;
319                    match value.field_at(i) {
320                        Some(child) => write_value(fmt, child, ancestry, entry_indent)?,
321                        None => fmt.write_str("<None>")?,
322                    }
323                    fmt.write_str(",\n")?;
324                }
325                Ok(())
326            })
327        }
328        VariantType::Unit => write!(f, "{qualified}"),
329    }
330}
331
332/// Writes a reflected [`Function`] value.
333///
334/// This method does not need cycle tracking as functions cannot contain other reflected values.
335#[cfg(feature = "functions")]
336fn write_function(f: &mut Formatter<'_>, func: &dyn Function) -> core::fmt::Result {
337    let pretty = func
338        .info()
339        .pretty_printer()
340        .include_fn_token()
341        .include_name();
342    // TODO: PrettyPrintFunctionInfo implements Debug but not Display
343    // so we just use the Debug formatting for now.
344    write!(f, "{pretty:?}")
345}
346
347impl Display for dyn Struct {
348    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
349        write_struct(f, self, &mut Vec::new(), 0)
350    }
351}
352
353impl Display for dyn TupleStruct {
354    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
355        write_tuple_struct(f, self, &mut Vec::new(), 0)
356    }
357}
358
359impl Display for dyn Tuple {
360    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
361        write_tuple(f, self, &mut Vec::new(), 0)
362    }
363}
364
365impl Display for dyn List {
366    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
367        write_list(f, self, &mut Vec::new(), 0)
368    }
369}
370
371impl Display for dyn Array {
372    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
373        write_array(f, self, &mut Vec::new(), 0)
374    }
375}
376
377impl Display for dyn Map {
378    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
379        write_map(f, self, &mut Vec::new(), 0)
380    }
381}
382
383impl Display for dyn Set {
384    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
385        write_set(f, self, &mut Vec::new(), 0)
386    }
387}
388
389impl Display for dyn Enum {
390    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
391        write_enum(f, self, &mut Vec::new(), 0)
392    }
393}
394
395#[cfg(feature = "functions")]
396impl Display for dyn Function {
397    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
398        write_function(f, self)
399    }
400}
401
402impl Display for ReflectRef<'_> {
403    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
404        let mut ancestry = Vec::new();
405        write_reflect_ref(f, self, &mut ancestry, 0)
406    }
407}
408
409impl Display for dyn PartialReflect {
410    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
411        write_value(f, self, &mut Vec::new(), 0)
412    }
413}
414
415impl Display for dyn Reflect {
416    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
417        Display::fmt(self as &dyn PartialReflect, f)
418    }
419}
420
421/// Display name for a reflected type, using its full type path.
422///
423/// Uses `fallback` when no type information is available (e.g. for dynamic values).
424fn display_type_name(type_info: Option<&TypeInfo>, fallback: &'static str) -> &'static str {
425    match type_info {
426        Some(info) => info.type_path(),
427        None => fallback,
428    }
429}
430
431/// Writes a two-space indent for each level of indentation requested to the formatter.
432fn write_indent(f: &mut Formatter<'_>, level: u32) -> core::fmt::Result {
433    for _ in 0..level {
434        f.write_str("  ")?;
435    }
436    Ok(())
437}
438
439/// Writes a delimited, indented block to `f`.
440///
441/// The `prefix{separator}{open}` is written first, then the closure writes
442/// entries (which should use `entry_indent` for indentation), and finally
443/// the closing `close` character is written at `indent`.
444///
445/// The caller must handle the empty case itself; this helper always emits
446/// a multi-line block.
447fn write_delimited_block(
448    f: &mut Formatter<'_>,
449    prefix: &str,
450    separator: &str,
451    open: char,
452    close: char,
453    indent: u32,
454    write_entries: impl FnOnce(&mut Formatter<'_>, u32) -> core::fmt::Result,
455) -> core::fmt::Result {
456    let entry_indent = indent + 1;
457    writeln!(f, "{prefix}{separator}{open}")?;
458    write_entries(f, entry_indent)?;
459    write_indent(f, indent)?;
460    f.write_char(close)
461}
462
463/// Writes an opaque reflected value by delegating to its [`Debug`] implementation.
464///
465/// Single-line debug output is written directly. Multi-line debug output has
466/// continuation lines indented one level deeper than `indent` (i.e. at the
467/// same visual level as entries inside a container whose closing delimiter
468/// sits at `indent`).
469///
470/// Leading whitespace is trimmed.
471fn write_opaque(
472    f: &mut Formatter<'_>,
473    value: &dyn PartialReflect,
474    indent: u32,
475) -> core::fmt::Result {
476    let debug = alloc::format!("{value:?}");
477    let trimmed = debug.trim();
478
479    // Fast path for single-line debug output
480    if trimmed.len() == debug.len() && !trimmed.contains('\n') {
481        return f.write_str(&debug);
482    }
483
484    // Multi-line: indent continuation lines to `indent + 1`
485    // (one level deeper than the enclosing container closing delimiter).
486    for (i, line) in trimmed.lines().enumerate() {
487        if i > 0 {
488            f.write_char('\n')?;
489        }
490        if line.trim().is_empty() {
491            continue;
492        }
493        if i > 0 {
494            write_indent(f, indent + 1)?;
495        }
496        // The first line (i == 0) is inline (no extra indent).
497        f.write_str(line)?;
498    }
499    Ok(())
500}
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505    use crate::{
506        array::DynamicArray, map::DynamicMap, set::DynamicSet, structs::DynamicStruct,
507        tuple::DynamicTuple, tuple_struct::DynamicTupleStruct, Reflect,
508    };
509    use alloc::{
510        boxed::Box,
511        collections::BTreeMap,
512        format,
513        string::{String, ToString},
514        vec,
515    };
516    use bevy_platform::collections::HashSet;
517    use disqualified::ShortName;
518
519    #[derive(Reflect, PartialEq, Eq, Hash)]
520    #[reflect(Hash)]
521    struct Inner {
522        a: u32,
523        b: String,
524    }
525
526    #[derive(Reflect)]
527    struct Outer {
528        name: String,
529        inner: Inner,
530        list: Vec<i32>,
531    }
532
533    #[derive(Reflect)]
534    struct GraphNode {
535        value: u32,
536        children: Vec<GraphNode>,
537    }
538
539    #[derive(Reflect)]
540    struct EmptyStruct {}
541
542    #[derive(Reflect)]
543    struct MutuallyRecursiveA {
544        items: Vec<MutuallyRecursiveB>,
545    }
546
547    #[derive(Reflect)]
548    struct MutuallyRecursiveB {
549        items: Vec<MutuallyRecursiveA>,
550    }
551
552    #[derive(Reflect)]
553    struct Newtype(u32);
554
555    #[derive(Reflect)]
556    struct EmptyTupleStruct();
557
558    #[derive(Reflect)]
559    enum MyEnum {
560        Unit,
561        Tuple(u32, String),
562        Struct { x: i32, inner: Inner },
563        EmptyTuple(),
564        EmptyStruct {},
565    }
566
567    #[test]
568    fn opaque_values_use_debug() {
569        assert_eq!(format!("{}", &42u32 as &dyn PartialReflect), "42");
570        assert_eq!(
571            format!("{}", &"hi".to_string() as &dyn PartialReflect),
572            "\"hi\""
573        );
574        assert_eq!(format!("{}", &true as &dyn PartialReflect), "true");
575    }
576
577    #[test]
578    fn flat_struct() {
579        let value = Inner {
580            a: 1,
581            b: "two".to_string(),
582        };
583        assert_eq!(
584            format!("{}", &value as &dyn PartialReflect),
585            "bevy_reflect::display::tests::Inner {\n  a: 1,\n  b: \"two\",\n}"
586        );
587    }
588
589    #[test]
590    fn empty_containers_collapse_to_one_line() {
591        assert_eq!(
592            format!("{}", &EmptyStruct {} as &dyn PartialReflect),
593            "bevy_reflect::display::tests::EmptyStruct {}"
594        );
595        assert_eq!(
596            format!("{}", &EmptyTupleStruct() as &dyn PartialReflect),
597            "bevy_reflect::display::tests::EmptyTupleStruct()"
598        );
599        assert_eq!(
600            format!("{}", &Vec::<i32>::new() as &dyn PartialReflect),
601            "[]"
602        );
603        assert_eq!(format!("{}", &[0i32; 0] as &dyn PartialReflect), "[]");
604        assert_eq!(format!("{}", &() as &dyn PartialReflect), "()");
605        assert_eq!(
606            format!("{}", &BTreeMap::<u32, u32>::new() as &dyn PartialReflect),
607            "{}"
608        );
609        assert_eq!(
610            format!("{}", &HashSet::<u32>::new() as &dyn PartialReflect),
611            "{}"
612        );
613    }
614
615    #[test]
616    fn empty_enum_variants() {
617        assert_eq!(
618            format!("{}", &MyEnum::EmptyTuple() as &dyn PartialReflect),
619            "bevy_reflect::display::tests::MyEnum::EmptyTuple()"
620        );
621        assert_eq!(
622            format!("{}", &MyEnum::EmptyStruct {} as &dyn PartialReflect),
623            "bevy_reflect::display::tests::MyEnum::EmptyStruct {}"
624        );
625    }
626
627    #[test]
628    fn newtype_struct() {
629        assert_eq!(
630            format!("{}", &Newtype(5) as &dyn PartialReflect),
631            "bevy_reflect::display::tests::Newtype(\n  5,\n)"
632        );
633    }
634
635    #[test]
636    fn list_of_scalars() {
637        assert_eq!(
638            format!("{}", &vec![1, 2, 3] as &dyn PartialReflect),
639            "[\n  1,\n  2,\n  3,\n]"
640        );
641    }
642
643    #[test]
644    fn map_entries() {
645        let mut map = BTreeMap::new();
646        map.insert(1u32, "one".to_string());
647        map.insert(2u32, "two".to_string());
648        assert_eq!(
649            format!("{}", &map as &dyn PartialReflect),
650            "{\n  1: \"one\",\n  2: \"two\",\n}"
651        );
652    }
653
654    #[test]
655    fn enum_variants() {
656        assert_eq!(
657            format!("{}", &MyEnum::Unit as &dyn PartialReflect),
658            "bevy_reflect::display::tests::MyEnum::Unit"
659        );
660        assert_eq!(
661            format!(
662                "{}",
663                &MyEnum::Tuple(7, "t".to_string()) as &dyn PartialReflect
664            ),
665            "bevy_reflect::display::tests::MyEnum::Tuple(\n  7,\n  \"t\",\n)"
666        );
667    }
668
669    #[test]
670    fn nested_values_are_indented_per_level() {
671        let value = Outer {
672            name: "hello".to_string(),
673            inner: Inner {
674                a: 1,
675                b: "two".to_string(),
676            },
677            list: vec![10, 20],
678        };
679
680        let expected = "\
681bevy_reflect::display::tests::Outer {
682  name: \"hello\",
683  inner: bevy_reflect::display::tests::Inner {
684    a: 1,
685    b: \"two\",
686  },
687  list: [
688    10,
689    20,
690  ],
691}";
692        assert_eq!(format!("{}", &value as &dyn PartialReflect), expected);
693    }
694
695    #[test]
696    fn deeply_nested_enum_struct_variant() {
697        let value = MyEnum::Struct {
698            x: -1,
699            inner: Inner {
700                a: 2,
701                b: "q".to_string(),
702            },
703        };
704
705        let expected = "\
706bevy_reflect::display::tests::MyEnum::Struct {
707  x: -1,
708  inner: bevy_reflect::display::tests::Inner {
709    a: 2,
710    b: \"q\",
711  },
712}";
713        assert_eq!(format!("{}", &value as &dyn PartialReflect), expected);
714    }
715
716    #[test]
717    fn type_name_uses_full_path() {
718        let value = Inner {
719            a: 1,
720            b: "two".to_string(),
721        };
722        assert_eq!(
723            format!("{}", &value as &dyn PartialReflect),
724            "bevy_reflect::display::tests::Inner {\n  a: 1,\n  b: \"two\",\n}"
725        );
726    }
727
728    #[test]
729    fn shortname_collapses_type_paths() {
730        let value = Outer {
731            name: "hello".to_string(),
732            inner: Inner {
733                a: 1,
734                b: "two".to_string(),
735            },
736            list: vec![10, 20],
737        };
738
739        let full = format!("{}", &value as &dyn PartialReflect);
740        // Verify the full version actually has paths to collapse
741        assert!(full.contains("bevy_reflect::display::tests::Outer"));
742        assert!(full.contains("bevy_reflect::display::tests::Inner"));
743
744        let short = ShortName::from(full.as_str()).to_string();
745        assert_eq!(
746            short,
747            concat!(
748                "Outer {\n",
749                "  name: \"hello\",\n",
750                "  inner: Inner {\n",
751                "    a: 1,\n",
752                "    b: \"two\",\n",
753                "  },\n",
754                "  list: [\n",
755                "    10,\n",
756                "    20,\n",
757                "  ],\n",
758                "}"
759            )
760        );
761    }
762
763    #[derive(Reflect, Clone)]
764    #[reflect(opaque, Debug)]
765    struct TrailingNewlineDebug;
766
767    impl core::fmt::Debug for TrailingNewlineDebug {
768        fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
769            writeln!(f, "value")
770        }
771    }
772
773    #[derive(Reflect)]
774    struct HoldsTrailingNewline {
775        op: TrailingNewlineDebug,
776    }
777
778    #[test]
779    fn opaque_debug_trailing_newline_does_not_orphan_comma() {
780        assert_eq!(
781            format!("{}", &TrailingNewlineDebug as &dyn PartialReflect),
782            "value"
783        );
784        assert_eq!(
785            format!(
786                "{}",
787                &HoldsTrailingNewline {
788                    op: TrailingNewlineDebug
789                } as &dyn PartialReflect
790            ),
791            "bevy_reflect::display::tests::HoldsTrailingNewline {\n  op: value,\n}"
792        );
793    }
794
795    #[derive(Reflect, Clone)]
796    #[reflect(opaque, Debug)]
797    struct TrailingNewlineThenSpaces;
798
799    impl core::fmt::Debug for TrailingNewlineThenSpaces {
800        fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
801            write!(f, "value\n  ")
802        }
803    }
804
805    #[derive(Reflect)]
806    struct HoldsTrailingNewlineThenSpaces {
807        op: TrailingNewlineThenSpaces,
808    }
809
810    #[test]
811    fn opaque_debug_trailing_whitespace_does_not_orphan_comma() {
812        assert_eq!(
813            format!("{}", &TrailingNewlineThenSpaces as &dyn PartialReflect),
814            "value"
815        );
816        assert_eq!(
817            format!(
818                "{}",
819                &HoldsTrailingNewlineThenSpaces {
820                    op: TrailingNewlineThenSpaces
821                } as &dyn PartialReflect
822            ),
823            "bevy_reflect::display::tests::HoldsTrailingNewlineThenSpaces {\n  op: value,\n}"
824        );
825    }
826
827    #[derive(Reflect, Clone)]
828    #[reflect(opaque, Debug)]
829    struct MultiLineDebug;
830
831    impl core::fmt::Debug for MultiLineDebug {
832        fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
833            write!(f, "line1\nline2")
834        }
835    }
836
837    #[derive(Reflect)]
838    struct HoldsMultiLine {
839        op: MultiLineDebug,
840    }
841
842    #[test]
843    fn multi_line_opaque_value_indents_continuation_lines() {
844        assert_eq!(
845            format!("{}", &MultiLineDebug as &dyn PartialReflect),
846            "line1\n  line2"
847        );
848        assert_eq!(
849            format!(
850                "{}",
851                &HoldsMultiLine { op: MultiLineDebug } as &dyn PartialReflect
852            ),
853            "bevy_reflect::display::tests::HoldsMultiLine {\n  op: line1\n    line2,\n}"
854        );
855    }
856
857    #[derive(Reflect, Clone)]
858    #[reflect(opaque, Debug)]
859    struct BlankInteriorLineDebug;
860
861    impl core::fmt::Debug for BlankInteriorLineDebug {
862        fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
863            write!(f, "line1\n   \nline2")
864        }
865    }
866
867    #[derive(Reflect)]
868    struct HoldsBlankInterior {
869        op: BlankInteriorLineDebug,
870    }
871
872    #[test]
873    fn opaque_debug_blank_interior_line_carries_no_trailing_whitespace() {
874        assert_eq!(
875            format!("{}", &BlankInteriorLineDebug as &dyn PartialReflect),
876            "line1\n\n  line2"
877        );
878        assert_eq!(
879            format!(
880                "{}",
881                &HoldsBlankInterior {
882                    op: BlankInteriorLineDebug
883                } as &dyn PartialReflect
884            ),
885            "bevy_reflect::display::tests::HoldsBlankInterior {\n  op: line1\n\n    line2,\n}"
886        );
887        let formatted = format!(
888            "{}",
889            &HoldsBlankInterior {
890                op: BlankInteriorLineDebug
891            } as &dyn PartialReflect
892        );
893        for line in formatted.lines() {
894            assert_eq!(
895                line.trim_end(),
896                line,
897                "line has trailing whitespace: {line:?}"
898            );
899        }
900    }
901
902    #[derive(Reflect, Clone)]
903    #[reflect(opaque, Debug)]
904    struct LeadingNewlineDebug;
905
906    impl core::fmt::Debug for LeadingNewlineDebug {
907        fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
908            write!(f, "\nvalue")
909        }
910    }
911
912    #[derive(Reflect)]
913    struct HoldsLeadingNewline {
914        op: LeadingNewlineDebug,
915    }
916
917    #[test]
918    fn opaque_debug_leading_newline_does_not_leave_trailing_whitespace() {
919        assert_eq!(
920            format!("{}", &LeadingNewlineDebug as &dyn PartialReflect),
921            "value"
922        );
923        assert_eq!(
924            format!(
925                "{}",
926                &HoldsLeadingNewline {
927                    op: LeadingNewlineDebug
928                } as &dyn PartialReflect
929            ),
930            "bevy_reflect::display::tests::HoldsLeadingNewline {\n  op: value,\n}"
931        );
932    }
933
934    #[test]
935    fn multi_line_struct_as_list_element() {
936        let value = vec![
937            Inner {
938                a: 1,
939                b: "x".to_string(),
940            },
941            Inner {
942                a: 2,
943                b: "y".to_string(),
944            },
945        ];
946        let expected = "\
947[
948  bevy_reflect::display::tests::Inner {
949    a: 1,
950    b: \"x\",
951  },
952  bevy_reflect::display::tests::Inner {
953    a: 2,
954    b: \"y\",
955  },
956]";
957        assert_eq!(format!("{}", &value as &dyn PartialReflect), expected);
958    }
959
960    #[test]
961    fn multi_line_struct_as_map_value() {
962        let mut map = BTreeMap::new();
963        map.insert(
964            1u32,
965            Inner {
966                a: 10,
967                b: "x".to_string(),
968            },
969        );
970        let expected = "\
971{
972  1: bevy_reflect::display::tests::Inner {
973    a: 10,
974    b: \"x\",
975  },
976}";
977        assert_eq!(format!("{}", &map as &dyn PartialReflect), expected);
978    }
979
980    #[test]
981    fn map_with_struct_key() {
982        let mut map = DynamicMap::default();
983        map.insert(
984            Inner {
985                a: 1,
986                b: "key".to_string(),
987            },
988            "value".to_string(),
989        );
990        assert_eq!(
991            format!("{}", &map as &dyn PartialReflect),
992            "{\n  bevy_reflect::display::tests::Inner {\n    a: 1,\n    b: \"key\",\n  }: \"value\",\n}"
993        );
994    }
995
996    #[test]
997    fn array_of_scalars() {
998        let array = DynamicArray::new(
999            vec![
1000                Box::new(1u32) as Box<dyn PartialReflect>,
1001                Box::new(2u32),
1002                Box::new(3u32),
1003            ]
1004            .into_boxed_slice(),
1005        );
1006        assert_eq!(
1007            format!("{}", &array as &dyn PartialReflect),
1008            "[\n  1,\n  2,\n  3,\n]"
1009        );
1010    }
1011
1012    #[test]
1013    fn set_with_one_element() {
1014        let mut set = DynamicSet::default();
1015        set.insert(42u32);
1016        assert_eq!(format!("{}", &set as &dyn PartialReflect), "{\n  42,\n}");
1017    }
1018
1019    #[test]
1020    fn set_with_struct_element() {
1021        let mut set = DynamicSet::default();
1022        set.insert(Inner {
1023            a: 10,
1024            b: "x".to_string(),
1025        });
1026        assert_eq!(
1027            format!("{}", &set as &dyn PartialReflect),
1028            "{\n  bevy_reflect::display::tests::Inner {\n    a: 10,\n    b: \"x\",\n  },\n}"
1029        );
1030    }
1031
1032    #[test]
1033    fn dynamic_struct_fallback_name() {
1034        let mut dyn_struct = DynamicStruct::default();
1035        dyn_struct.insert("x", 1u32);
1036        dyn_struct.insert("y", 2u32);
1037        assert_eq!(
1038            format!("{}", &dyn_struct as &dyn PartialReflect),
1039            "<Unknown Struct> {\n  x: 1,\n  y: 2,\n}"
1040        );
1041    }
1042
1043    #[test]
1044    fn dynamic_tuple_displays() {
1045        let mut dyn_tuple = DynamicTuple::default();
1046        dyn_tuple.insert(42u32);
1047        dyn_tuple.insert("hi".to_string());
1048        assert_eq!(
1049            format!("{}", &dyn_tuple as &dyn PartialReflect),
1050            "(\n  42,\n  \"hi\",\n)"
1051        );
1052    }
1053
1054    #[test]
1055    fn dynamic_tuple_struct_fallback_name() {
1056        let mut dyn_tuple_struct = DynamicTupleStruct::default();
1057        dyn_tuple_struct.insert(42u32);
1058        dyn_tuple_struct.insert("hi".to_string());
1059        assert_eq!(
1060            format!("{}", &dyn_tuple_struct as &dyn PartialReflect),
1061            "<Unknown TupleStruct>(\n  42,\n  \"hi\",\n)"
1062        );
1063    }
1064
1065    #[test]
1066    fn reflect_ref_displays() {
1067        let value = Inner {
1068            a: 1,
1069            b: "two".to_string(),
1070        };
1071        let reflect_ref = value.reflect_ref();
1072        assert_eq!(
1073            format!("{reflect_ref}"),
1074            "bevy_reflect::display::tests::Inner {\n  a: 1,\n  b: \"two\",\n}"
1075        );
1076    }
1077
1078    #[test]
1079    fn cycle_detection_truncates_on_repeated_type_path() {
1080        let child = GraphNode {
1081            value: 2,
1082            children: Vec::new(),
1083        };
1084        let parent = GraphNode {
1085            value: 1,
1086            children: vec![child],
1087        };
1088        assert_eq!(
1089            format!("{}", &parent as &dyn PartialReflect),
1090            concat!(
1091                "bevy_reflect::display::tests::GraphNode {\n",
1092                "  value: 1,\n",
1093                "  children: [\n",
1094                "    bevy_reflect::display::tests::GraphNode { ... },\n",
1095                "  ],\n",
1096                "}"
1097            )
1098        );
1099    }
1100
1101    #[test]
1102    fn cross_type_cycle_detection_truncates() {
1103        let inner = MutuallyRecursiveA { items: Vec::new() };
1104        let b = MutuallyRecursiveB { items: vec![inner] };
1105        let outer = MutuallyRecursiveA { items: vec![b] };
1106        assert_eq!(
1107            format!("{}", &outer as &dyn PartialReflect),
1108            concat!(
1109                "bevy_reflect::display::tests::MutuallyRecursiveA {\n",
1110                "  items: [\n",
1111                "    bevy_reflect::display::tests::MutuallyRecursiveB {\n",
1112                "      items: [\n",
1113                "        bevy_reflect::display::tests::MutuallyRecursiveA { ... },\n",
1114                "      ],\n",
1115                "    },\n",
1116                "  ],\n",
1117                "}"
1118            )
1119        );
1120    }
1121
1122    #[cfg(feature = "functions")]
1123    mod functions {
1124        use super::*;
1125        use crate::func::IntoFunction;
1126
1127        #[test]
1128        fn named_function() {
1129            fn greet(name: &String) -> String {
1130                format!("Hello, {name}!")
1131            }
1132            let function = greet.into_function();
1133            assert_eq!(
1134                format!("{}", &function as &dyn PartialReflect),
1135                "fn bevy_reflect::display::tests::functions::named_function::greet(_: &alloc::string::String) -> alloc::string::String"
1136            );
1137        }
1138
1139        #[test]
1140        fn anonymous_function() {
1141            let function = (|a: i32, b: i32| a + b).into_function();
1142            assert_eq!(
1143                format!("{}", &function as &dyn PartialReflect),
1144                "fn _(_: i32, _: i32) -> i32"
1145            );
1146        }
1147
1148        #[test]
1149        fn overloaded_function() {
1150            fn add_i32(a: i32, b: i32) -> i32 {
1151                a + b
1152            }
1153            fn add_f32(a: f32, b: f32) -> f32 {
1154                a + b
1155            }
1156            let function = add_i32
1157                .into_function()
1158                .with_overload(add_f32)
1159                .with_name("add");
1160            assert_eq!(
1161                format!("{}", &function as &dyn PartialReflect),
1162                "fn add {(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32}"
1163            );
1164        }
1165    }
1166}