bevy_utils/
short_names.rs

1/// Shortens a type name to remove all module paths.
2///
3/// The short name of a type is its full name as returned by
4/// [`std::any::type_name`], but with the prefix of all paths removed. For
5/// example, the short name of `alloc::vec::Vec<core::option::Option<u32>>`
6/// would be `Vec<Option<u32>>`.
7pub fn get_short_name(full_name: &str) -> String {
8    // Generics result in nested paths within <..> blocks.
9    // Consider "bevy_render::camera::camera::extract_cameras<bevy_render::camera::bundle::Camera3d>".
10    // To tackle this, we parse the string from left to right, collapsing as we go.
11    let mut index: usize = 0;
12    let end_of_string = full_name.len();
13    let mut parsed_name = String::new();
14
15    while index < end_of_string {
16        let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
17
18        // Collapse everything up to the next special character,
19        // then skip over it
20        if let Some(special_character_index) = rest_of_string.find(|c: char| {
21            (c == ' ')
22                || (c == '<')
23                || (c == '>')
24                || (c == '(')
25                || (c == ')')
26                || (c == '[')
27                || (c == ']')
28                || (c == ',')
29                || (c == ';')
30        }) {
31            let segment_to_collapse = rest_of_string
32                .get(0..special_character_index)
33                .unwrap_or_default();
34            parsed_name += collapse_type_name(segment_to_collapse);
35            // Insert the special character
36            let special_character =
37                &rest_of_string[special_character_index..=special_character_index];
38            parsed_name.push_str(special_character);
39
40            match special_character {
41                ">" | ")" | "]"
42                    if rest_of_string[special_character_index + 1..].starts_with("::") =>
43                {
44                    parsed_name.push_str("::");
45                    // Move the index past the "::"
46                    index += special_character_index + 3;
47                }
48                // Move the index just past the special character
49                _ => index += special_character_index + 1,
50            }
51        } else {
52            // If there are no special characters left, we're done!
53            parsed_name += collapse_type_name(rest_of_string);
54            index = end_of_string;
55        }
56    }
57    parsed_name
58}
59
60#[inline(always)]
61fn collapse_type_name(string: &str) -> &str {
62    // Enums types are retained.
63    // As heuristic, we assume the enum type to be uppercase.
64    let mut segments = string.rsplit("::");
65    let (last, second_last): (&str, Option<&str>) = (segments.next().unwrap(), segments.next());
66    let Some(second_last) = second_last else {
67        return last;
68    };
69
70    if second_last.starts_with(char::is_uppercase) {
71        let index = string.len() - last.len() - second_last.len() - 2;
72        &string[index..]
73    } else {
74        last
75    }
76}
77
78#[cfg(test)]
79mod name_formatting_tests {
80    use super::get_short_name;
81
82    #[test]
83    fn trivial() {
84        assert_eq!(get_short_name("test_system"), "test_system");
85    }
86
87    #[test]
88    fn path_separated() {
89        assert_eq!(
90            get_short_name("bevy_prelude::make_fun_game"),
91            "make_fun_game".to_string()
92        );
93    }
94
95    #[test]
96    fn tuple_type() {
97        assert_eq!(
98            get_short_name("(String, String)"),
99            "(String, String)".to_string()
100        );
101    }
102
103    #[test]
104    fn array_type() {
105        assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]".to_string());
106    }
107
108    #[test]
109    fn trivial_generics() {
110        assert_eq!(get_short_name("a<B>"), "a<B>".to_string());
111    }
112
113    #[test]
114    fn multiple_type_parameters() {
115        assert_eq!(get_short_name("a<B, C>"), "a<B, C>".to_string());
116    }
117
118    #[test]
119    fn enums() {
120        assert_eq!(get_short_name("Option::None"), "Option::None".to_string());
121        assert_eq!(
122            get_short_name("Option::Some(2)"),
123            "Option::Some(2)".to_string()
124        );
125        assert_eq!(
126            get_short_name("bevy_render::RenderSet::Prepare"),
127            "RenderSet::Prepare".to_string()
128        );
129    }
130
131    #[test]
132    fn generics() {
133        assert_eq!(
134            get_short_name("bevy_render::camera::camera::extract_cameras<bevy_render::camera::bundle::Camera3d>"),
135            "extract_cameras<Camera3d>".to_string()
136        );
137    }
138
139    #[test]
140    fn nested_generics() {
141        assert_eq!(
142            get_short_name("bevy::mad_science::do_mad_science<mad_science::Test<mad_science::Tube>, bavy::TypeSystemAbuse>"),
143            "do_mad_science<Test<Tube>, TypeSystemAbuse>".to_string()
144        );
145    }
146
147    #[test]
148    fn sub_path_after_closing_bracket() {
149        assert_eq!(
150            get_short_name("bevy_asset::assets::Assets<bevy_scene::dynamic_scene::DynamicScene>::asset_event_system"),
151            "Assets<DynamicScene>::asset_event_system".to_string()
152        );
153        assert_eq!(
154            get_short_name("(String, String)::default"),
155            "(String, String)::default".to_string()
156        );
157        assert_eq!(
158            get_short_name("[i32; 16]::default"),
159            "[i32; 16]::default".to_string()
160        );
161    }
162}