[libcxx-commits] [libcxx] [libc++][format] Improves diagnostics. (PR #127234)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Thu Mar 27 10:09:48 PDT 2025


================
@@ -205,6 +208,149 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu
     return basic_format_arg<_Context>{__arg, __value};
 }
 
+// Helper function that issues a diagnostic when __formattable_with<_Tp, _Context> is false.
+//
+// Since it's quite easy to make a mistake writing a formatter specialization
+// this function tries to give a better explanation. This should improve the
+// diagnostics when trying to format type that has no properly specialized
+// formatter.
+template <class _Context, class _Tp>
+[[noreturn]] _LIBCPP_HIDE_FROM_ABI constexpr void __diagnose_invalid_formatter() {
+  using _Formatter = typename _Context::template formatter_type<remove_const_t<_Tp>>;
+  constexpr bool __is_disabled =
+      !is_default_constructible_v<_Formatter> && !is_copy_constructible_v<_Formatter> &&
+      !is_move_constructible_v<_Formatter> && !is_copy_assignable_v<_Formatter> && !is_move_assignable_v<_Formatter>;
+  constexpr bool __is_semiregular = semiregular<_Formatter>;
+
+  constexpr bool __has_parse_function =
+      requires(_Formatter& __f, basic_format_parse_context<typename _Context::char_type> __pc) {
+        { __f.parse(__pc) };
+      };
+  constexpr bool __correct_parse_function_return_type =
+      requires(_Formatter& __f, basic_format_parse_context<typename _Context::char_type> __pc) {
+        { __f.parse(__pc) } -> same_as<typename decltype(__pc)::iterator>;
+      };
+
+  // The reason these static_asserts are placed in an if-constexpr-chain is to
+  // only show one error. For example, when the formatter is not specialized it
+  // would show all static_assert messages. With this chain the compiler only
+  // evaluates one static_assert.
+
+  if constexpr (__is_disabled)
+
+    static_assert(
+#  if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600
+        sizeof(_Tp) == 0
+#  else
+        false
+#  endif
+        ,
+        "The required formatter specialization has not been provided.");
+  else if constexpr (!__is_semiregular)
+    static_assert(
+#  if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600
+        sizeof(_Tp) == 0
+#  else
+        false
+#  endif
+        ,
+        "The required formatter specialization is not semiregular.");
+
+  else if constexpr (!__has_parse_function)
+    static_assert(
+#  if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600
+        sizeof(_Tp) == 0
+#  else
+        false
+#  endif
+        ,
+        "The required formatter specialization does not have a parse function taking the proper arguments.");
+  else if constexpr (!__correct_parse_function_return_type)
+    static_assert(
+#  if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600
+        sizeof(_Tp) == 0
+#  else
+        false
+#  endif
+        ,
+        "The required formatter specialization's parse function does not return the required type.");
+
+  else {
+    // During constant evaluation this function is called, but the format
+    // member function has not been evaluated. This means these functions
+    // can't be evaluated at that time.
+    //
+    // Note this else branch should never been taken during constant
+    // eveluation, the static_asserts in one of the branches above should
+    // trigger.
+    constexpr bool __has_format_function = requires(_Formatter& __f, _Tp&& __t, _Context __fc) {
+      { __f.format(__t, __fc) };
+    };
+    constexpr bool __is_format_function_const_qualified = requires(const _Formatter& __cf, _Tp&& __t, _Context __fc) {
+      { __cf.format(__t, __fc) };
+    };
+    constexpr bool __correct_format_function_return_type = requires(_Formatter& __f, _Tp&& __t, _Context __fc) {
+      { __f.format(__t, __fc)->template same_as<typename _Context::iterator> };
+    };
+
+    if constexpr (!__has_format_function)
+      static_assert(
+#  if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600
+          sizeof(_Tp) == 0
+#  else
+          false
+#  endif
+          ,
+          "The required formatter specialization does not have a format function taking the proper arguments.");
+    else if constexpr (!__is_format_function_const_qualified)
+      static_assert(
+#  if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600
+          sizeof(_Tp) == 0
+#  else
+          false
+#  endif
+          ,
+          "The required formatter specialization's format function is not const qualified.");
+    else if constexpr (!__correct_format_function_return_type)
+      static_assert(
+#  if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600
+          sizeof(_Tp) == 0
+#  else
+          false
+#  endif
+          ,
+          "The required formatter specialization's format function does not return the required type.");
+
+    else
+      // This should not happen; it makes sure the code is ill-formed.
+      static_assert(
+#  if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600
+          sizeof(_Tp) == 0
+#  else
+          false
+#  endif
+          ,
+          "The required formatter specialization is not formattable with its context.");
+  }
+}
+
+// The Psuedo constructor is constrained per [format.arg]/4.
----------------
ldionne wrote:

```suggestion
// The pseudo constructor is constrained per [format.arg]/4.
```

https://github.com/llvm/llvm-project/pull/127234


More information about the libcxx-commits mailing list