pretty_name/
type_name.rs

1use syn::*;
2
3/// Get the human-friendly type name of given type `T`, removing visual clutter such as
4/// full module paths.
5/// 
6/// # Examples
7/// ```rust
8/// use pretty_name::type_name;
9/// assert_eq!(type_name::<Option<i32>>(), "Option<i32>");
10/// assert_eq!(type_name::<&str>(), "&str");
11/// assert_eq!(type_name::<Vec<Box<dyn std::fmt::Debug>>>(), "Vec<Box<dyn Debug>>");
12/// ```
13pub fn type_name<T: ?Sized + 'static>() -> String {
14    use std::any::TypeId;
15    use std::cell::RefCell;
16    use std::collections::HashMap;
17    use std::collections::hash_map::Entry;
18
19    thread_local!(
20        static TYPE_NAME_CACHE: RefCell<HashMap<TypeId, String>> =
21            RefCell::new(HashMap::new()));
22
23    TYPE_NAME_CACHE.with_borrow_mut(|cache| match cache.entry(TypeId::of::<T>()) {
24        Entry::Occupied(entry) =>
25            entry.get().clone(),
26        Entry::Vacant(entry) =>
27            entry.insert(match syn::parse_str::<Type>(std::any::type_name::<T>()) {
28                Ok(mut ty) => {
29                    truncate_type(&mut ty);
30
31                    // Use rustfmt to get a nicely formatted type string.
32                    // rustfmt only accepts full source files, so we wrap the type in a dummy function.
33                    use quote::quote;
34                    use rust_format::Formatter as _;
35                    let format_result = rust_format::RustFmt::default()
36                        .format_tokens(quote!(fn main() -> #ty {}))
37                        .unwrap_or("<error>".to_string());
38                    let start = const { "fn main() -> ".len() };
39                    let end = format_result.len() - const { " {}\r\n".len() };
40                    format_result[start..end].to_owned()
41                },
42                Err(_) => "<error>".to_owned(),
43            }).clone(),
44    })
45}
46
47/// Get the human-friendly type name of the given value, removing visual clutter such as
48/// full module paths.
49/// 
50/// # Examples
51/// ```rust
52/// use pretty_name::type_name_of_val;
53/// let value = vec![1, 2, 3];
54/// assert_eq!(type_name_of_val(&value), "Vec<i32>");
55/// ```
56pub fn type_name_of_val<T: ?Sized + 'static>(_: &T) -> String {
57    type_name::<T>()
58}
59
60fn truncate_type(ty: &mut Type) {
61    match *ty {
62        Type::Infer(_) |
63        Type::Macro(_) |
64        Type::Never(_) |
65        Type::Verbatim(_) => {}
66
67        Type::Array(TypeArray { ref mut elem, .. }) |
68        Type::Group(TypeGroup { group_token: _, ref mut elem }) |
69        Type::Paren(TypeParen { paren_token: _, ref mut elem }) |
70        Type::Ptr(TypePtr { ref mut elem, .. }) |
71        Type::Slice(TypeSlice { ref mut elem, .. }) => truncate_type(elem),
72
73        Type::Reference(TypeReference {
74            ref mut lifetime,
75            ref mut elem,
76            ..
77        }) => {
78            *lifetime = None;
79            truncate_type(elem);
80        }
81
82        Type::Path(ref mut ty) => truncate_path(&mut ty.path),
83
84        Type::BareFn(ref mut ty) => {
85            for input in ty.inputs.iter_mut() {
86                truncate_type(&mut input.ty);
87            }
88
89            if let ReturnType::Type(_, ref mut ty) = ty.output {
90                truncate_type(ty.as_mut());
91            }
92        }
93
94        Type::ImplTrait(ref mut ty) => {
95            for bound in ty.bounds.iter_mut() {
96                if let &mut TypeParamBound::Trait(ref mut trt) = bound {
97                    truncate_path(&mut trt.path);
98                }
99            }
100        }
101
102        Type::TraitObject(ref mut ty) => {
103            for bound in ty.bounds.iter_mut() {
104                if let &mut TypeParamBound::Trait(ref mut trt) = bound {
105                    truncate_path(&mut trt.path);
106                }
107            }
108        }
109
110        Type::Tuple(ref mut ty) => {
111            for elem in ty.elems.iter_mut() {
112                truncate_type(elem);
113            }
114        }
115
116        _ => { /* non_exhaustive variants */ }
117    }
118}
119
120fn truncate_path(path: &mut Path) {
121    let path_mut = path;
122    let path = std::mem::replace(
123        path_mut,
124        Path {
125            leading_colon: None,
126            segments: Default::default(),
127        });
128
129    let Some(mut last_segment) = path.segments.into_iter().last() else {
130        path_mut.leading_colon = None;
131        path_mut.segments = Default::default();
132        return;
133    };
134
135    match last_segment.arguments {
136        PathArguments::None => {}
137        PathArguments::AngleBracketed(ref mut args) => {
138            for arg in args.args.iter_mut() {
139                match *arg {
140                    GenericArgument::Type(ref mut ty) => truncate_type(ty),
141                    GenericArgument::AssocType(ref mut ty) => {
142                        truncate_type(&mut ty.ty)
143                    }
144                    _ => {}
145                }
146            }
147        }
148        PathArguments::Parenthesized(ref mut args) => {
149            for input in args.inputs.iter_mut() {
150                truncate_type(input);
151            }
152            if let ReturnType::Type(_, ref mut output) = args.output {
153                truncate_type(output);
154            }
155        }
156    }
157
158    path_mut.leading_colon = None;
159    path_mut.segments = Some(last_segment).into_iter().collect();
160}
161
162#[cfg(test)]
163mod test {
164    use super::type_name;
165
166    #[test]
167    fn test_type_name() {
168        // ===== Primitives =====
169        assert_eq!(type_name::<i32>(), "i32");
170        assert_eq!(type_name::<bool>(), "bool");
171
172        // ===== Unsized Primitives =====
173        assert_eq!(type_name::<str>(), "str");
174        assert_eq!(type_name::<[i32]>(), "[i32]");
175
176        // ===== References - Immutable =====
177        assert_eq!(type_name::<&i32>(), "&i32");
178        assert_eq!(type_name::<&str>(), "&str");
179        // Lifetime elision - 'static should be removed
180        assert_eq!(type_name::<&'static str>(), "&str");
181        // Multiple levels of indirection
182        assert_eq!(type_name::<&&&str>(), "&&&str");
183        // Reference to unsized slice
184        assert_eq!(type_name::<&[i32]>(), "&[i32]");
185
186        // ===== References - Mutable =====
187        assert_eq!(type_name::<&mut String>(), "&mut String");
188        assert_eq!(type_name::<&mut &str>(), "&mut &str");
189        assert_eq!(type_name::<&mut str>(), "&mut str");
190        assert_eq!(type_name::<&mut [i32]>(), "&mut [i32]");
191
192        // ===== Raw Pointers =====
193        assert_eq!(type_name::<*const i32>(), "*const i32");
194        assert_eq!(type_name::<*mut i32>(), "*mut i32");
195        assert_eq!(type_name::<*const str>(), "*const str");
196        assert_eq!(type_name::<*mut [u8]>(), "*mut [u8]");
197        // Nested raw pointers
198        assert_eq!(type_name::<*const *mut i32>(), "*const *mut i32");
199        // Mixed pointer types
200        assert_eq!(type_name::<*const &str>(), "*const &str");
201        assert_eq!(type_name::<&*const i32>(), "&*const i32");
202
203        // ===== Arrays =====
204        assert_eq!(type_name::<[i32; 5]>(), "[i32; 5]");
205        assert_eq!(type_name::<[bool; 0]>(), "[bool; 0]");
206        assert_eq!(type_name::<&[i32; 3]>(), "&[i32; 3]");
207        assert_eq!(type_name::<&mut [i32; 5]>(), "&mut [i32; 5]");
208        // Nested arrays
209        assert_eq!(type_name::<[[i32; 2]; 3]>(), "[[i32; 2]; 3]");
210        assert_eq!(type_name::<[[[u8; 2]; 3]; 4]>(), "[[[u8; 2]; 3]; 4]");
211        // Array of tuples
212        assert_eq!(type_name::<[(i32, bool); 10]>(), "[(i32, bool); 10]");
213
214        // ===== Tuples =====
215        assert_eq!(type_name::<()>(), "()");
216        assert_eq!(type_name::<(i32,)>(), "(i32,)");
217        assert_eq!(type_name::<(i32, String, bool)>(), "(i32, String, bool)");
218        // Nested tuples
219        assert_eq!(type_name::<(i32, (String, bool))>(), "(i32, (String, bool))");
220        // Tuples with references to unsized
221        assert_eq!(type_name::<(&str, &[u8])>(), "(&str, &[u8])");
222        assert_eq!(type_name::<(&mut String, &i32)>(), "(&mut String, &i32)");
223
224        // ===== Generic Containers =====
225        assert_eq!(type_name::<Option<i32>>(), "Option<i32>");
226        assert_eq!(type_name::<Option<&str>>(), "Option<&str>");
227        assert_eq!(type_name::<Result<i32, String>>(), "Result<i32, String>");
228        assert_eq!(type_name::<Result<(), ()>>(), "Result<(), ()>");
229        assert_eq!(type_name::<Vec<i32>>(), "Vec<i32>");
230        assert_eq!(type_name::<std::collections::HashMap<String, i32>>(), "HashMap<String, i32>");
231        assert_eq!(type_name::<std::collections::BTreeMap<String, i32>>(), "BTreeMap<String, i32>");
232
233        // ===== Function Pointers =====
234        assert_eq!(type_name::<fn()>(), "fn()");
235        assert_eq!(type_name::<fn(i32) -> i32>(), "fn(i32) -> i32");
236        assert_eq!(type_name::<fn(i32, String, bool)>(), "fn(i32, String, bool)");
237        assert_eq!(type_name::<fn(&str) -> String>(), "fn(&str) -> String");
238        assert_eq!(type_name::<fn(&mut i32)>(), "fn(&mut i32)");
239        assert_eq!(type_name::<fn(*const i32) -> *mut i32>(), "fn(*const i32) -> *mut i32");
240        // Function returning function
241        assert_eq!(type_name::<fn() -> fn(i32) -> i32>(), "fn() -> fn(i32) -> i32");
242        assert_eq!(type_name::<fn(fn(i32) -> i32) -> i32>(), "fn(fn(i32) -> i32) -> i32");
243        // Unsafe and extern functions
244        assert_eq!(type_name::<unsafe fn()>(), "unsafe fn()");
245        assert_eq!(type_name::<extern "C" fn(i32) -> i32>(), "extern \"C\" fn(i32) -> i32");
246        assert_eq!(type_name::<unsafe extern "C" fn(i32)>(), "unsafe extern \"C\" fn(i32)");
247
248        // ===== Trait Objects =====
249        assert_eq!(type_name::<Box<dyn std::fmt::Debug>>(), "Box<dyn Debug>");
250        assert_eq!(type_name::<&dyn std::fmt::Display>(), "&dyn Display");
251        assert_eq!(type_name::<&mut dyn std::io::Write>(), "&mut dyn Write");
252        assert_eq!(type_name::<Box<dyn std::fmt::Debug + Send>>(), "Box<dyn Debug + Send>");
253        assert_eq!(type_name::<Box<dyn std::fmt::Debug + Send + Sync>>(), "Box<dyn Debug + Send + Sync>");
254        assert_eq!(type_name::<dyn std::fmt::Debug>(), "dyn Debug");
255        assert_eq!(type_name::<dyn std::fmt::Debug + Send>(), "dyn Debug + Send");
256
257        // ===== Smart Pointers =====
258        assert_eq!(type_name::<Box<i32>>(), "Box<i32>");
259        assert_eq!(type_name::<Box<str>>(), "Box<str>");
260        assert_eq!(type_name::<Box<[i32]>>(), "Box<[i32]>");
261        assert_eq!(type_name::<std::rc::Rc<String>>(), "Rc<String>");
262        assert_eq!(type_name::<std::sync::Arc<String>>(), "Arc<String>");
263        assert_eq!(type_name::<std::cell::RefCell<i32>>(), "RefCell<i32>");
264
265        // ===== Nested Generic Types =====
266        assert_eq!(type_name::<Vec<Vec<String>>>(), "Vec<Vec<String>>");
267        assert_eq!(type_name::<Vec<Vec<Vec<i32>>>>(), "Vec<Vec<Vec<i32>>>");
268        assert_eq!(type_name::<Option<Result<i32, String>>>(), "Option<Result<i32, String>>");
269        assert_eq!(type_name::<Box<Option<Vec<String>>>>(), "Box<Option<Vec<String>>>");
270        assert_eq!(type_name::<Option<Box<dyn std::fmt::Debug>>>(), "Option<Box<dyn Debug>>");
271        assert_eq!(type_name::<Vec<Option<&str>>>(), "Vec<Option<&str>>");
272
273        // ===== Composite Structures =====
274        assert_eq!(type_name::<(Option<i32>, Result<String, ()>)>(), "(Option<i32>, Result<String, ()>)");
275        assert_eq!(type_name::<&[(i32, String)]>(), "&[(i32, String)]");
276        assert_eq!(type_name::<[(Option<i32>, &str); 5]>(), "[(Option<i32>, &str); 5]");
277        assert_eq!(type_name::<std::collections::HashMap<String, Vec<i32>>>(), "HashMap<String, Vec<i32>>");
278        assert_eq!(type_name::<&[Option<Result<i32, String>>]>(), "&[Option<Result<i32, String>>]");
279        // Function pointers in containers
280        assert_eq!(type_name::<Vec<fn(i32) -> i32>>(), "Vec<fn(i32) -> i32>");
281        assert_eq!(type_name::<Option<fn() -> String>>(), "Option<fn() -> String>");
282
283        // ===== Path Simplification =====
284        assert_eq!(type_name::<std::vec::Vec<i32>>(), "Vec<i32>");
285        assert_eq!(type_name::<std::string::String>(), "String");
286        assert_eq!(type_name::<std::boxed::Box<i32>>(), "Box<i32>");
287        // Nested qualified paths
288        assert_eq!(type_name::<Result<Vec<u8>, std::io::Error>>(), "Result<Vec<u8>, Error>");
289        assert_eq!(type_name::<std::collections::HashMap<std::string::String, std::vec::Vec<i32>>>(), "HashMap<String, Vec<i32>>");
290
291        // ===== Extreme Nesting & Combinations =====
292        assert_eq!(type_name::<Vec<Option<Result<Box<dyn std::fmt::Debug>, String>>>>(), "Vec<Option<Result<Box<dyn Debug>, String>>>");
293        assert_eq!(type_name::<&[Option<&[(i32, &str)]>]>(), "&[Option<&[(i32, &str)]>]");
294        assert_eq!(type_name::<fn(Vec<&str>) -> Option<Result<i32, Box<dyn std::error::Error>>>>(), "fn(Vec<&str>) -> Option<Result<i32, Box<dyn Error>>>");
295
296        // ===== Edge Cases =====
297        assert_eq!(type_name::<[(); 5]>(), "[(); 5]");
298        assert_eq!(type_name::<std::marker::PhantomData<i32>>(), "PhantomData<i32>");
299        assert_eq!(type_name::<std::marker::PhantomData<&str>>(), "PhantomData<&str>");
300    }
301}