fmtlib / fmt

A modern formatting library
https://fmt.dev
Other
19.84k stars 2.42k forks source link

Added std::type_info formatter #3978

Closed matt77hias closed 1 month ago

matt77hias commented 1 month ago
matt77hias commented 1 month ago

Ok still WIP in order to support wchar_t. Ignore for now, but please leave it open so I can borrow the CI.

matt77hias commented 1 month ago

Update: removed union occurrences as well for MSVC

phprus commented 1 month ago

I think formatting std::type_info should not change the output in MSVC. Removing all substrings may distort the result.

Before your changes, std::exception formatting used minimal additional memory allocations (only for Itanium ABI).. I think it makes sense to keep this behavior.

matt77hias commented 1 month ago

I think formatting std::type_info should not change the output in MSVC. Removing all substrings may distort the result.

Note that prior to my changes, the class or struct prefix was already removed. Removing just these prefixes is somewhat arbitrary:

Before your changes, std::exception formatting used minimal additional memory allocations (only for Itanium ABI).. I think it makes sense to keep this behavior.

I think we can output the characters one by one while jumping over the occurrences instead of using a std::basic_string.

I do wonder whether I could erronously strip part of the actual identifier, instead of language keywords. All of the patterns do contain a space character that should prevent this. MSVC does not seem to use spaces before and after ,, < ~and >~. But that is all speculative of me.

phprus commented 1 month ago

Suggested changes

Create function:

namespace detail {

template <typename Char, typename OutputIt>
auto write_demangled_name(OutputIt out,
                          std::type_info& ti) -> OutputIt {
#  ifdef FMT_HAS_ABI_CXA_DEMANGLE
    int status = 0;
    std::size_t size = 0;
    std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
        abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);

    string_view demangled_name_view;
    if (demangled_name_ptr) {
      demangled_name_view = demangled_name_ptr.get();

      // Normalization of stdlib inline namespace names.
      // libc++ inline namespaces.
      //  std::__1::*       -> std::*
      //  std::__1::__fs::* -> std::*
      // libstdc++ inline namespaces.
      //  std::__cxx11::*             -> std::*
      //  std::filesystem::__cxx11::* -> std::filesystem::*
      if (demangled_name_view.starts_with("std::")) {
        char* begin = demangled_name_ptr.get();
        char* to = begin + 5;  // std::
        for (char *from = to, *end = begin + demangled_name_view.size();
             from < end;) {
          // This is safe, because demangled_name is NUL-terminated.
          if (from[0] == '_' && from[1] == '_') {
            char* next = from + 1;
            while (next < end && *next != ':') next++;
            if (next[0] == ':' && next[1] == ':') {
              from = next + 2;
              continue;
            }
          }
          *to++ = *from++;
        }
        demangled_name_view = {begin, detail::to_unsigned(to - begin)};
      }
    } else {
      demangled_name_view = string_view(ti.name());
    }
    return detail::write_bytes<Char>(out, demangled_name_view);
#  elif FMT_MSC_VERSION
    std::string demangled_name(ti.name());
    details::remove_all_substrings(demangled_name, "class ");
    details::remove_all_substrings(demangled_name, "enum ");
    details::remove_all_substrings(demangled_name, "struct ");
    details::remove_all_substrings(demangled_name, "union ");
    return detail::write_bytes<Char>(out, demangled_name);
#  else
    return detail::write_bytes<Char>(out, string_view(ti.name()));
#  endif
}

}

and reuse it in formatters.

We don't need to use detail::string_literal and std::basic_string<Char> since std::type_info::name() always returns const char *

matt77hias commented 1 month ago

Out of scope improvements:

phprus commented 1 month ago

LGTM

matt77hias commented 1 month ago

It is actually implicitly tested already via the exception formatter. But I can add another one. I'll reuse one of the exceptions because even something as simple as int outputs something different on Clang and GCC.