ast_demangle/rust_v0/
display.rs

1//! Pretty printing demangled symbol names.
2
3use crate::rust_v0::{
4    Abi, BasicType, Const, ConstFields, DynBounds, DynTrait, DynTraitAssocBinding, FnSig, GenericArg, Path, Type,
5};
6use std::any;
7use std::cell::Cell;
8use std::fmt::{self, Display, Formatter, Write};
9
10/// Denote the style for displaying the symbol.
11#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
12pub enum Style {
13    /// Omit enclosing namespaces to get a shorter name.
14    Short,
15    /// Omit crate hashes and const value types. This matches rustc-demangle’s `{}` format.
16    Normal,
17    /// Show crate hashes and const value types. This matches rustc-demangle’s `{:#}` format. Note that even with this
18    /// style, impl paths are still omitted.
19    Long,
20}
21
22fn display_fn(f: impl Fn(&mut Formatter) -> fmt::Result) -> impl Display {
23    struct Wrapper<F>(F);
24
25    impl<F: Fn(&mut Formatter) -> fmt::Result> Display for Wrapper<F> {
26        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
27            self.0(f)
28        }
29    }
30
31    Wrapper(f)
32}
33
34fn display_separated_list(values: impl IntoIterator<Item = impl Display>, separator: impl Display) -> impl Display {
35    let values = Cell::new(Some(values));
36
37    display_fn(move |f| {
38        let mut iter = values.take().unwrap().into_iter();
39
40        if let Some(first) = iter.next() {
41            first.fmt(f)?;
42
43            for value in iter {
44                separator.fmt(f)?;
45                value.fmt(f)?;
46            }
47        }
48
49        Ok(())
50    })
51}
52
53pub fn display_path<'a>(path: &'a Path, style: Style, bound_lifetime_depth: u64, in_value: bool) -> impl Display + 'a {
54    display_fn(move |f| match path {
55        Path::CrateRoot(identifier) => match style {
56            Style::Short | Style::Normal => f.write_str(&identifier.name),
57            Style::Long => {
58                write!(f, "{}[{:x}]", identifier.name, identifier.disambiguator)
59            }
60        },
61        Path::InherentImpl { type_, .. } => {
62            write!(f, "<{}>", display_type(type_, style, bound_lifetime_depth))
63        }
64        Path::TraitImpl { type_, trait_, .. } | Path::TraitDefinition { type_, trait_ } => {
65            write!(
66                f,
67                "<{} as {}>",
68                display_type(type_, style, bound_lifetime_depth),
69                display_path(trait_, style, bound_lifetime_depth, false)
70            )
71        }
72        Path::Nested {
73            namespace,
74            path,
75            identifier,
76        } => match namespace {
77            b'A'..=b'Z' => {
78                display_path(path, style, bound_lifetime_depth, in_value).fmt(f)?;
79
80                f.write_str("::{")?;
81
82                match namespace {
83                    b'C' => f.write_str("closure")?,
84                    b'S' => f.write_str("shim")?,
85                    _ => f.write_char(char::from(*namespace))?,
86                }
87
88                if !identifier.name.is_empty() {
89                    write!(f, ":{}", identifier.name)?;
90                }
91
92                write!(f, "#{}}}", identifier.disambiguator)
93            }
94            b'a'..=b'z' => {
95                if matches!(style, Style::Normal | Style::Long)
96                    || matches!(
97                        path.as_ref(),
98                        Path::InherentImpl { .. }
99                            | Path::TraitImpl { .. }
100                            | Path::TraitDefinition { .. }
101                            | Path::Generic { .. }
102                    )
103                {
104                    display_path(path, style, bound_lifetime_depth, in_value).fmt(f)?;
105
106                    if identifier.name.is_empty() {
107                        Ok(())
108                    } else {
109                        write!(f, "::{}", identifier.name)
110                    }
111                } else if identifier.name.is_empty() {
112                    display_path(path, style, bound_lifetime_depth, in_value).fmt(f)
113                } else {
114                    write!(f, "{}", identifier.name)
115                }
116            }
117            _ => Err(fmt::Error),
118        },
119        Path::Generic { path, generic_args } => {
120            display_path(path, style, bound_lifetime_depth, in_value).fmt(f)?;
121
122            if in_value {
123                f.write_str("::")?;
124            }
125
126            write!(
127                f,
128                "<{}>",
129                display_separated_list(
130                    generic_args.iter().map(|generic_arg| display_generic_arg(
131                        generic_arg,
132                        style,
133                        bound_lifetime_depth,
134                    )),
135                    ", "
136                )
137            )
138        }
139    })
140}
141
142fn display_lifetime(lifetime: u64, bound_lifetime_depth: u64) -> impl Display {
143    display_fn(move |f| {
144        f.write_char('\'')?;
145
146        if lifetime == 0 {
147            f.write_char('_')
148        } else if let Some(depth) = bound_lifetime_depth.checked_sub(lifetime) {
149            if depth < 26 {
150                f.write_char(char::from(b'a' + u8::try_from(depth).unwrap()))
151            } else {
152                write!(f, "_{}", depth)
153            }
154        } else {
155            Err(fmt::Error)
156        }
157    })
158}
159
160pub fn display_generic_arg<'a>(
161    generic_arg: &'a GenericArg,
162    style: Style,
163    bound_lifetime_depth: u64,
164) -> impl Display + 'a {
165    display_fn(move |f| match generic_arg {
166        GenericArg::Lifetime(lifetime) => display_lifetime(*lifetime, bound_lifetime_depth).fmt(f),
167        GenericArg::Type(type_) => display_type(type_, style, bound_lifetime_depth).fmt(f),
168        GenericArg::Const(const_) => display_const(const_, style, bound_lifetime_depth, false).fmt(f),
169    })
170}
171
172fn display_binder(bound_lifetimes: u64, bound_lifetime_depth: u64) -> impl Display {
173    display_fn(move |f| {
174        write!(
175            f,
176            "for<{}>",
177            display_separated_list(
178                (1..=bound_lifetimes)
179                    .rev()
180                    .map(|i| display_lifetime(i, bound_lifetime_depth + bound_lifetimes)),
181                ", "
182            )
183        )
184    })
185}
186
187pub fn display_type<'a>(type_: &'a Type, style: Style, bound_lifetime_depth: u64) -> impl Display + 'a {
188    display_fn(move |f| match type_ {
189        Type::Basic(basic_type) => display_basic_type(*basic_type).fmt(f),
190        Type::Named(path) => display_path(path, style, bound_lifetime_depth, false).fmt(f),
191        Type::Array(type_, length) => {
192            write!(
193                f,
194                "[{}; {}]",
195                display_type(type_, style, bound_lifetime_depth),
196                display_const(length, style, bound_lifetime_depth, true)
197            )
198        }
199        Type::Slice(type_) => {
200            write!(f, "[{}]", display_type(type_, style, bound_lifetime_depth))
201        }
202        Type::Tuple(tuple_types) => {
203            write!(
204                f,
205                "({}",
206                display_separated_list(
207                    tuple_types
208                        .iter()
209                        .map(|type_| { display_type(type_, style, bound_lifetime_depth) }),
210                    ", "
211                )
212            )?;
213
214            if tuple_types.len() == 1 {
215                f.write_char(',')?;
216            }
217
218            f.write_char(')')
219        }
220        Type::Ref { lifetime, type_ } => {
221            f.write_char('&')?;
222
223            if *lifetime != 0 {
224                write!(f, "{} ", display_lifetime(*lifetime, bound_lifetime_depth))?;
225            }
226
227            display_type(type_, style, bound_lifetime_depth).fmt(f)
228        }
229        Type::RefMut { lifetime, type_ } => {
230            f.write_char('&')?;
231
232            if *lifetime != 0 {
233                write!(f, "{} ", display_lifetime(*lifetime, bound_lifetime_depth))?;
234            }
235
236            write!(f, "mut {}", display_type(type_, style, bound_lifetime_depth))
237        }
238        Type::PtrConst(type_) => {
239            write!(f, "*const {}", display_type(type_, style, bound_lifetime_depth))
240        }
241        Type::PtrMut(type_) => {
242            write!(f, "*mut {}", display_type(type_, style, bound_lifetime_depth))
243        }
244        Type::Fn(fn_sig) => display_fn_sig(fn_sig, style, bound_lifetime_depth).fmt(f),
245        Type::DynTrait { dyn_bounds, lifetime } => {
246            display_dyn_bounds(dyn_bounds, style, bound_lifetime_depth).fmt(f)?;
247
248            if *lifetime == 0 {
249                Ok(())
250            } else {
251                write!(f, " + {}", display_lifetime(*lifetime, bound_lifetime_depth))
252            }
253        }
254    })
255}
256
257pub fn display_basic_type(basic_type: BasicType) -> impl Display {
258    display_fn(move |f| {
259        f.write_str(match basic_type {
260            BasicType::I8 => "i8",
261            BasicType::Bool => "bool",
262            BasicType::Char => "char",
263            BasicType::F64 => "f64",
264            BasicType::Str => "str",
265            BasicType::F32 => "f32",
266            BasicType::U8 => "u8",
267            BasicType::Isize => "isize",
268            BasicType::Usize => "usize",
269            BasicType::I32 => "i32",
270            BasicType::U32 => "u32",
271            BasicType::I128 => "i128",
272            BasicType::U128 => "u128",
273            BasicType::I16 => "i16",
274            BasicType::U16 => "u16",
275            BasicType::Unit => "()",
276            BasicType::Ellipsis => "...",
277            BasicType::I64 => "i64",
278            BasicType::U64 => "u64",
279            BasicType::Never => "!",
280            BasicType::Placeholder => "_",
281        })
282    })
283}
284
285pub fn display_fn_sig<'a>(fn_sig: &'a FnSig, style: Style, bound_lifetime_depth: u64) -> impl Display + 'a {
286    display_fn(move |f| {
287        if fn_sig.bound_lifetimes != 0 {
288            write!(f, "{} ", display_binder(fn_sig.bound_lifetimes, bound_lifetime_depth))?;
289        }
290
291        let bound_lifetime_depth = bound_lifetime_depth + fn_sig.bound_lifetimes;
292
293        if fn_sig.is_unsafe {
294            f.write_str("unsafe ")?;
295        }
296
297        if let Some(abi) = &fn_sig.abi {
298            write!(f, "extern {} ", display_abi(abi))?;
299        }
300
301        write!(
302            f,
303            "fn({})",
304            display_separated_list(
305                fn_sig
306                    .argument_types
307                    .iter()
308                    .map(|type_| { display_type(type_, style, bound_lifetime_depth) }),
309                ", "
310            )
311        )?;
312
313        if let Type::Basic(BasicType::Unit) = fn_sig.return_type.as_ref() {
314            Ok(())
315        } else {
316            write!(
317                f,
318                " -> {}",
319                display_type(&fn_sig.return_type, style, bound_lifetime_depth)
320            )
321        }
322    })
323}
324
325fn display_abi<'a>(abi: &'a Abi) -> impl Display + 'a {
326    display_fn(move |f| {
327        f.write_char('"')?;
328
329        match abi {
330            Abi::C => f.write_char('C')?,
331            Abi::Named(name) => {
332                let mut iter = name.split('_');
333
334                f.write_str(iter.next().unwrap())?;
335
336                for item in iter {
337                    write!(f, "-{}", item)?;
338                }
339            }
340        }
341
342        f.write_char('"')
343    })
344}
345
346fn display_dyn_bounds<'a>(dyn_bounds: &'a DynBounds, style: Style, bound_lifetime_depth: u64) -> impl Display + 'a {
347    display_fn(move |f| {
348        f.write_str("dyn ")?;
349
350        if dyn_bounds.bound_lifetimes != 0 {
351            write!(
352                f,
353                "{} ",
354                display_binder(dyn_bounds.bound_lifetimes, bound_lifetime_depth)
355            )?;
356        }
357
358        let bound_lifetime_depth = bound_lifetime_depth + dyn_bounds.bound_lifetimes;
359
360        display_separated_list(
361            dyn_bounds
362                .dyn_traits
363                .iter()
364                .map(move |dyn_trait| display_dyn_trait(dyn_trait, style, bound_lifetime_depth)),
365            " + ",
366        )
367        .fmt(f)
368    })
369}
370
371fn display_dyn_trait<'a>(dyn_trait: &'a DynTrait, style: Style, bound_lifetime_depth: u64) -> impl Display + 'a {
372    display_fn(move |f| {
373        if dyn_trait.dyn_trait_assoc_bindings.is_empty() {
374            display_path(&dyn_trait.path, style, bound_lifetime_depth, false).fmt(f)
375        } else if let Path::Generic { path, generic_args } = dyn_trait.path.as_ref() {
376            write!(
377                f,
378                "{}<{}>",
379                display_path(path, style, bound_lifetime_depth, false),
380                display_separated_list(
381                    generic_args
382                        .iter()
383                        .map(Ok)
384                        .chain(dyn_trait.dyn_trait_assoc_bindings.iter().map(Err))
385                        .map(|value| {
386                            display_fn(move |f| match value {
387                                Ok(generic_arg) => display_generic_arg(generic_arg, style, bound_lifetime_depth).fmt(f),
388                                Err(dyn_trait_assoc_binding) => display_dyn_trait_assoc_binding(
389                                    dyn_trait_assoc_binding,
390                                    style,
391                                    bound_lifetime_depth,
392                                )
393                                .fmt(f),
394                            })
395                        }),
396                    ", "
397                )
398            )
399        } else {
400            write!(
401                f,
402                "{}<{}>",
403                display_path(&dyn_trait.path, style, bound_lifetime_depth, false),
404                display_separated_list(
405                    dyn_trait
406                        .dyn_trait_assoc_bindings
407                        .iter()
408                        .map(|dyn_trait_assoc_binding| {
409                            display_dyn_trait_assoc_binding(dyn_trait_assoc_binding, style, bound_lifetime_depth)
410                        }),
411                    ", "
412                )
413            )
414        }
415    })
416}
417
418fn display_dyn_trait_assoc_binding<'a>(
419    dyn_trait_assoc_binding: &'a DynTraitAssocBinding,
420    style: Style,
421    bound_lifetime_depth: u64,
422) -> impl Display + 'a {
423    display_fn(move |f| {
424        write!(
425            f,
426            "{} = {}",
427            dyn_trait_assoc_binding.name,
428            display_type(&dyn_trait_assoc_binding.type_, style, bound_lifetime_depth)
429        )
430    })
431}
432
433fn write_integer<T: Display>(f: &mut Formatter, value: T, style: Style) -> fmt::Result {
434    write!(f, "{}", value)?;
435
436    if matches!(style, Style::Long) {
437        f.write_str(any::type_name::<T>())
438    } else {
439        Ok(())
440    }
441}
442
443pub fn display_const<'a>(
444    const_: &'a Const,
445    style: Style,
446    bound_lifetime_depth: u64,
447    in_value: bool,
448) -> impl Display + 'a {
449    display_fn(move |f| match *const_ {
450        Const::I8(value) => write_integer(f, value, style),
451        Const::U8(value) => write_integer(f, value, style),
452        Const::Isize(value) => write_integer(f, value, style),
453        Const::Usize(value) => write_integer(f, value, style),
454        Const::I32(value) => write_integer(f, value, style),
455        Const::U32(value) => write_integer(f, value, style),
456        Const::I128(value) => write_integer(f, value, style),
457        Const::U128(value) => write_integer(f, value, style),
458        Const::I16(value) => write_integer(f, value, style),
459        Const::U16(value) => write_integer(f, value, style),
460        Const::I64(value) => write_integer(f, value, style),
461        Const::U64(value) => write_integer(f, value, style),
462        Const::Bool(value) => write!(f, "{}", value),
463        Const::Char(value) => write!(f, "{:?}", value),
464        Const::Str(ref value) => {
465            if in_value {
466                write!(f, "*{:?}", value)
467            } else {
468                write!(f, "{{*{:?}}}", value)
469            }
470        }
471        Const::Ref(ref value) => {
472            if let Const::Str(value) = value.as_ref() {
473                write!(f, "{:?}", value)
474            } else if in_value {
475                write!(f, "&{}", display_const(value, style, bound_lifetime_depth, true))
476            } else {
477                write!(f, "{{&{}}}", display_const(value, style, bound_lifetime_depth, true))
478            }
479        }
480        Const::RefMut(ref value) => {
481            let inner = display_const(value, style, bound_lifetime_depth, true);
482
483            if in_value {
484                write!(f, "&mut {}", inner)
485            } else {
486                write!(f, "{{&mut {}}}", inner)
487            }
488        }
489        Const::Array(ref items) => {
490            let inner = display_separated_list(
491                items
492                    .iter()
493                    .map(|item| display_const(item, style, bound_lifetime_depth, true)),
494                ", ",
495            );
496
497            if in_value {
498                write!(f, "[{}]", inner)
499            } else {
500                write!(f, "{{[{}]}}", inner)
501            }
502        }
503        Const::Tuple(ref items) => {
504            let inner = display_separated_list(
505                items
506                    .iter()
507                    .map(|item| display_const(item, style, bound_lifetime_depth, true)),
508                ", ",
509            );
510
511            if in_value {
512                write!(f, "({}", inner)?;
513
514                f.write_str(if items.len() == 1 { ",)" } else { ")" })
515            } else {
516                write!(f, "{{({}", inner)?;
517
518                f.write_str(if items.len() == 1 { ",)}" } else { ")}" })
519            }
520        }
521        Const::NamedStruct { ref path, ref fields } => {
522            let path = display_path(path, style, bound_lifetime_depth, true);
523            let fields = display_const_fields(fields, style, bound_lifetime_depth);
524
525            if in_value {
526                write!(f, "{}{}", path, fields)
527            } else {
528                write!(f, "{{{}{}}}", path, fields)
529            }
530        }
531        Const::Placeholder => write!(f, "_"),
532    })
533}
534
535fn display_const_fields<'a>(fields: &'a ConstFields, style: Style, bound_lifetime_depth: u64) -> impl Display + 'a {
536    display_fn(move |f| match fields {
537        ConstFields::Unit => Ok(()),
538        ConstFields::Tuple(fields) => write!(
539            f,
540            "({})",
541            display_separated_list(
542                fields
543                    .iter()
544                    .map(|field| display_const(field, style, bound_lifetime_depth, true)),
545                ", "
546            )
547        ),
548        ConstFields::Struct(fields) => {
549            if fields.is_empty() {
550                // Matches the behavior of `rustc-demangle`.
551                write!(f, " {{  }}")
552            } else {
553                write!(
554                    f,
555                    " {{ {} }}",
556                    display_separated_list(
557                        fields.iter().map(|(name, value)| {
558                            display_fn(move |f| {
559                                write!(
560                                    f,
561                                    "{}: {}",
562                                    name,
563                                    display_const(value, style, bound_lifetime_depth, true)
564                                )
565                            })
566                        }),
567                        ", "
568                    )
569                )
570            }
571        }
572    })
573}
574
575#[cfg(test)]
576mod tests {
577    use super::Style;
578    use crate::rust_v0::Symbol;
579    use std::fmt::Write;
580
581    #[test]
582    fn test_display_path() {
583        let test_cases = [(
584            "_RINvCsd5QWgxammnl_7example3fooNcNtINtNtCs454gRYH7d6L_4core6result6ResultllE2Ok0EB2_",
585            (
586                "foo::<Result<i32, i32>::Ok>",
587                "example::foo::<core::result::Result<i32, i32>::Ok>",
588                "example[9884ce86676676d1]::foo::<core[2f8af133219d6c11]::result::Result<i32, i32>::Ok>",
589            ),
590        )];
591
592        let mut buffer = String::new();
593
594        for (symbol, expected) in test_cases {
595            let symbol = Symbol::parse_from_str(symbol).unwrap().0;
596
597            write!(buffer, "{}", symbol.display(Style::Short)).unwrap();
598
599            let length_1 = buffer.len();
600
601            write!(buffer, "{}", symbol.display(Style::Normal)).unwrap();
602
603            let length_2 = buffer.len();
604
605            write!(buffer, "{}", symbol.display(Style::Long)).unwrap();
606
607            assert_eq!(
608                (&buffer[..length_1], &buffer[length_1..length_2], &buffer[length_2..]),
609                expected
610            );
611
612            buffer.clear();
613        }
614    }
615
616    #[test]
617    fn test_display_lifetime() {
618        #[track_caller]
619        fn check(lifetime: u64, bound_lifetime_depth: u64, expected: &str) {
620            assert_eq!(
621                super::display_lifetime(lifetime, bound_lifetime_depth).to_string(),
622                expected
623            );
624        }
625
626        check(0, 0, "'_");
627        check(0, 1, "'_");
628        check(0, 2, "'_");
629
630        check(1, 1, "'a");
631        check(1, 2, "'b");
632        check(1, 3, "'c");
633
634        check(2, 2, "'a");
635        check(2, 3, "'b");
636        check(2, 4, "'c");
637    }
638
639    #[test]
640    fn test_display_binder() {
641        #[track_caller]
642        fn check(bound_lifetimes: u64, bound_lifetime_depth: u64, expected: &str) {
643            assert_eq!(
644                super::display_binder(bound_lifetimes, bound_lifetime_depth).to_string(),
645                expected
646            );
647        }
648
649        check(0, 0, "for<>");
650        check(0, 1, "for<>");
651        check(0, 2, "for<>");
652
653        check(1, 0, "for<'a>");
654        check(1, 1, "for<'b>");
655        check(1, 2, "for<'c>");
656
657        check(2, 0, "for<'a, 'b>");
658        check(2, 1, "for<'b, 'c>");
659        check(2, 2, "for<'c, 'd>");
660    }
661}