pretty_name/
type_name.rs

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