Skip to content

[libc++][format] Fixes formatting code units as integers. #73396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 29, 2023

Conversation

mordante
Copy link
Member

This paper was voted in as a DR, so it's retroactively enabled back to C++20; the C++ version that introduced std::format.

Implements:

  • P2909R4 Fix formatting of code units as integers (Dude, where’s my char?)

This paper was voted in as a DR, so it's retroactively enabled back
to C++20; the C++ version that introduced std::format.

Implements:
- P2909R4 Fix formatting of code units as integers (Dude, where’s my ``char``?)
@mordante mordante requested a review from a team as a code owner November 25, 2023 15:57
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Nov 25, 2023
@llvmbot
Copy link
Member

llvmbot commented Nov 25, 2023

@llvm/pr-subscribers-libcxx

Author: Mark de Wever (mordante)

Changes

This paper was voted in as a DR, so it's retroactively enabled back to C++20; the C++ version that introduced std::format.

Implements:

  • P2909R4 Fix formatting of code units as integers (Dude, where’s my char?)

Patch is 23.75 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/73396.diff

12 Files Affected:

  • (modified) libcxx/docs/FeatureTestMacroTable.rst (+1-1)
  • (modified) libcxx/docs/ReleaseNotes/18.rst (+1)
  • (modified) libcxx/docs/Status/Cxx2cPapers.csv (+1-1)
  • (modified) libcxx/docs/Status/FormatIssues.csv (+1-1)
  • (modified) libcxx/include/__format/format_arg_store.h (+7-3)
  • (modified) libcxx/include/__format/formatter_char.h (+8-11)
  • (modified) libcxx/include/version (+1-1)
  • (modified) libcxx/test/std/language.support/support.limits/support.limits.general/.version.compile.pass.cpp (+15-33)
  • (modified) libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp (+15-33)
  • (added) libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.fsigned-char.pass.cpp (+131)
  • (added) libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.funsigned-char.pass.cpp (+131)
  • (modified) libcxx/utils/generate_feature_test_macro_components.py (-1)
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 3b2dff3108b0f60..d09f65b7cadc0e2 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -236,7 +236,7 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_format``                                *unimplemented*
     --------------------------------------------------- -----------------
-    ``__cpp_lib_format_uchar``                          *unimplemented*
+    ``__cpp_lib_format_uchar``                          ``202311L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_generic_unordered_lookup``              ``201811L``
     --------------------------------------------------- -----------------
diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index 7c4b578bd6fa3ec..36ce4a46f76e403 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -53,6 +53,7 @@ Implemented Papers
 - P2918R2 - Runtime format strings II
 - P2871R3 - Remove Deprecated Unicode Conversion Facets from C++26
 - P2870R3 - Remove basic_string::reserve()
+- P2909R4 - Fix formatting of code units as integers (Dude, where’s my ``char``?)
 
 
 Improvements and New Features
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index 65c3391526596d9..1d071b7ebcb4a76 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -32,7 +32,7 @@
 "`P2546R5 <https://wg21.link/P2546R5>`__","LWG","Debugging Support","Kona November 2023","","",""
 "`P2905R2 <https://wg21.link/P2905R2>`__","LWG","Runtime format strings","Kona November 2023","","","|format| |DR|"
 "`P2918R2 <https://wg21.link/P2918R2>`__","LWG","Runtime format strings II","Kona November 2023","|Complete|","18.0","|format|"
-"`P2909R4 <https://wg21.link/P2909R4>`__","LWG","Fix formatting of code units as integers (Dude, where’s my ``char``?)","Kona November 2023","","","|format| |DR|"
+"`P2909R4 <https://wg21.link/P2909R4>`__","LWG","Fix formatting of code units as integers (Dude, where’s my ``char``?)","Kona November 2023","|Complete|","18.0","|format| |DR|"
 "`P0952R2 <https://wg21.link/P0952R2>`__","LWG","A new specification for ``std::generate_canonical``","Kona November 2023","","",""
 "`P2447R6 <https://wg21.link/P2447R6>`__","LWG","``std::span`` over an initializer list","Kona November 2023","","",""
 "`P2821R5 <https://wg21.link/P2821R5>`__","LWG","``span.at()``","Kona November 2023","","",""
diff --git a/libcxx/docs/Status/FormatIssues.csv b/libcxx/docs/Status/FormatIssues.csv
index 14ea7c4360a373f..005de97405f7ccd 100644
--- a/libcxx/docs/Status/FormatIssues.csv
+++ b/libcxx/docs/Status/FormatIssues.csv
@@ -19,7 +19,7 @@ Number,Name,Standard,Assignee,Status,First released version
 "`P2637R3 <https://wg21.link/P2637R3>`__","Member ``visit``","C++26","","",
 "`P2905R2 <https://wg21.link/P2905R2>`__","Runtime format strings","C++26 DR","Mark de Wever","|In Progress|"
 "`P2918R2 <https://wg21.link/P2918R2>`__","Runtime format strings II","C++26","Mark de Wever","|Complete|",18.0
-"`P2909R4 <https://wg21.link/P2909R4>`__","Fix formatting of code units as integers (Dude, where’s my ``char``?)","C++26 DR","Mark de Wever","|In Progress|"
+"`P2909R4 <https://wg21.link/P2909R4>`__","Fix formatting of code units as integers (Dude, where’s my ``char``?)","C++26 DR","Mark de Wever","|Complete|",18.0
 `P1361 <https://wg21.link/P1361>`_,"Integration of chrono with text formatting","C++20",Mark de Wever,|In Progress|,
 `P2372 <https://wg21.link/P2372>`__,"Fixing locale handling in chrono formatters","C++20",Mark de Wever,|In Progress|,
 "`P2419R2 <https://wg21.link/P2419R2>`__","Clarify handling of encodings in localized formatting of chrono types","C++23",
diff --git a/libcxx/include/__format/format_arg_store.h b/libcxx/include/__format/format_arg_store.h
index 15ec8eb0a7d838b..2962962ab5d1c23 100644
--- a/libcxx/include/__format/format_arg_store.h
+++ b/libcxx/include/__format/format_arg_store.h
@@ -172,9 +172,13 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu
   // __basic_format_arg_value.  First handle all types needing adjustment, the
   // final else requires no adjustment.
   if constexpr (__arg == __arg_t::__char_type)
-    // On some platforms initializing a wchar_t from a char is a narrowing
-    // conversion.
-    return basic_format_arg<_Context>{__arg, static_cast<typename _Context::char_type>(__value)};
+
+#  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+    if constexpr (same_as<typename _Context::char_type, wchar_t> && same_as<_Dp, char>)
+      return basic_format_arg<_Context>{__arg, static_cast<wchar_t>(static_cast<unsigned char>(__value))};
+    else
+#  endif
+      return basic_format_arg<_Context>{__arg, __value};
   else if constexpr (__arg == __arg_t::__int)
     return basic_format_arg<_Context>{__arg, static_cast<int>(__value)};
   else if constexpr (__arg == __arg_t::__long_long)
diff --git a/libcxx/include/__format/formatter_char.h b/libcxx/include/__format/formatter_char.h
index d6e61e8654493af..bc3962c87607119 100644
--- a/libcxx/include/__format/formatter_char.h
+++ b/libcxx/include/__format/formatter_char.h
@@ -21,7 +21,7 @@
 #include <__format/parser_std_format_spec.h>
 #include <__format/write_escaped.h>
 #include <__type_traits/conditional.h>
-#include <__type_traits/is_signed.h>
+#include <__type_traits/make_unsigned.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -51,22 +51,20 @@ struct _LIBCPP_TEMPLATE_VIS __formatter_char {
       return __formatter::__format_escaped_char(__value, __ctx.out(), __parser_.__get_parsed_std_specifications(__ctx));
 #  endif
 
-    if constexpr (sizeof(_CharT) <= sizeof(int))
-      // Promotes _CharT to an integral type. This reduces the number of
-      // instantiations of __format_integer reducing code size.
+    if constexpr (sizeof(_CharT) <= sizeof(unsigned))
       return __formatter::__format_integer(
-          static_cast<conditional_t<is_signed_v<_CharT>, int, unsigned>>(__value),
+          static_cast<unsigned>(static_cast<make_unsigned_t<_CharT>>(__value)),
           __ctx,
           __parser_.__get_parsed_std_specifications(__ctx));
     else
-      return __formatter::__format_integer(__value, __ctx, __parser_.__get_parsed_std_specifications(__ctx));
+      return __formatter::__format_integer(
+          static_cast<make_unsigned_t<_CharT>>(__value), __ctx, __parser_.__get_parsed_std_specifications(__ctx));
   }
 
   template <class _FormatContext>
   _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator format(char __value, _FormatContext& __ctx) const
-    requires(same_as<_CharT, wchar_t>)
-  {
-    return format(static_cast<wchar_t>(__value), __ctx);
+      requires(same_as<_CharT, wchar_t>) {
+    return format(static_cast<wchar_t>(static_cast<unsigned char>(__value)), __ctx);
   }
 
 #  if _LIBCPP_STD_VER >= 23
@@ -84,8 +82,7 @@ template <>
 struct _LIBCPP_TEMPLATE_VIS formatter<char, wchar_t> : public __formatter_char<wchar_t> {};
 
 template <>
-struct _LIBCPP_TEMPLATE_VIS formatter<wchar_t, wchar_t> : public __formatter_char<wchar_t> {
-};
+struct _LIBCPP_TEMPLATE_VIS formatter<wchar_t, wchar_t> : public __formatter_char<wchar_t> {};
 
 #  endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
 
diff --git a/libcxx/include/version b/libcxx/include/version
index ef01af753298222..e84790b888d3333 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -384,7 +384,7 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 # undef  __cpp_lib_execution
 // # define __cpp_lib_execution                            201902L
 // # define __cpp_lib_format                               202106L
-// # define __cpp_lib_format_uchar                         202311L
+# define __cpp_lib_format_uchar                         202311L
 # define __cpp_lib_generic_unordered_lookup             201811L
 # define __cpp_lib_int_pow2                             202002L
 # define __cpp_lib_integer_comparison_functions         202002L
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/.version.compile.pass.cpp
index 6aa31c60e863002..2486985cefaca0a 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/.version.compile.pass.cpp
@@ -55,17 +55,11 @@
 
 #elif TEST_STD_VER == 20
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should be defined in c++20"
-#   endif
-#   if __cpp_lib_format_uchar != 202311L
-#     error "__cpp_lib_format_uchar should have the value 202311L in c++20"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_format_uchar
+#   error "__cpp_lib_format_uchar should be defined in c++20"
+# endif
+# if __cpp_lib_format_uchar != 202311L
+#   error "__cpp_lib_format_uchar should have the value 202311L in c++20"
 # endif
 
 # ifdef __cpp_lib_saturation_arithmetic
@@ -74,17 +68,11 @@
 
 #elif TEST_STD_VER == 23
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should be defined in c++23"
-#   endif
-#   if __cpp_lib_format_uchar != 202311L
-#     error "__cpp_lib_format_uchar should have the value 202311L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_format_uchar
+#   error "__cpp_lib_format_uchar should be defined in c++23"
+# endif
+# if __cpp_lib_format_uchar != 202311L
+#   error "__cpp_lib_format_uchar should have the value 202311L in c++23"
 # endif
 
 # ifdef __cpp_lib_saturation_arithmetic
@@ -93,17 +81,11 @@
 
 #elif TEST_STD_VER > 23
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should be defined in c++26"
-#   endif
-#   if __cpp_lib_format_uchar != 202311L
-#     error "__cpp_lib_format_uchar should have the value 202311L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_format_uchar
+#   error "__cpp_lib_format_uchar should be defined in c++26"
+# endif
+# if __cpp_lib_format_uchar != 202311L
+#   error "__cpp_lib_format_uchar should have the value 202311L in c++26"
 # endif
 
 # if !defined(_LIBCPP_VERSION)
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index d7adf2941b62c61..a995795e305c49f 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -3383,17 +3383,11 @@
 #   error "__cpp_lib_format_ranges should not be defined before c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should be defined in c++20"
-#   endif
-#   if __cpp_lib_format_uchar != 202311L
-#     error "__cpp_lib_format_uchar should have the value 202311L in c++20"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_format_uchar
+#   error "__cpp_lib_format_uchar should be defined in c++20"
+# endif
+# if __cpp_lib_format_uchar != 202311L
+#   error "__cpp_lib_format_uchar should have the value 202311L in c++20"
 # endif
 
 # ifdef __cpp_lib_formatters
@@ -4778,17 +4772,11 @@
 #   error "__cpp_lib_format_ranges should have the value 202207L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should be defined in c++23"
-#   endif
-#   if __cpp_lib_format_uchar != 202311L
-#     error "__cpp_lib_format_uchar should have the value 202311L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_format_uchar
+#   error "__cpp_lib_format_uchar should be defined in c++23"
+# endif
+# if __cpp_lib_format_uchar != 202311L
+#   error "__cpp_lib_format_uchar should have the value 202311L in c++23"
 # endif
 
 # if !defined(_LIBCPP_VERSION)
@@ -6389,17 +6377,11 @@
 #   error "__cpp_lib_format_ranges should have the value 202207L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should be defined in c++26"
-#   endif
-#   if __cpp_lib_format_uchar != 202311L
-#     error "__cpp_lib_format_uchar should have the value 202311L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_format_uchar
-#     error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_format_uchar
+#   error "__cpp_lib_format_uchar should be defined in c++26"
+# endif
+# if __cpp_lib_format_uchar != 202311L
+#   error "__cpp_lib_format_uchar should have the value 202311L in c++26"
 # endif
 
 # if !defined(_LIBCPP_VERSION)
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.fsigned-char.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.fsigned-char.pass.cpp
new file mode 100644
index 000000000000000..933cb1ff962bb83
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.fsigned-char.pass.cpp
@@ -0,0 +1,131 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -fsigned-char
+
+// <format>
+
+// C++23 the formatter is a debug-enabled specialization.
+// [format.formatter.spec]:
+// Each header that declares the template `formatter` provides the following
+// enabled specializations:
+// The specializations
+//   template<> struct formatter<char, char>;
+//   template<> struct formatter<char, wchar_t>;
+//   template<> struct formatter<wchar_t, wchar_t>;
+
+// P2909R4 "Fix formatting of code units as integers (Dude, where’s my char?)"
+// changed the behaviour of char (and wchar_t) when their underlying type is signed.
+
+#include <format>
+#include <cassert>
+#include <concepts>
+#include <iterator>
+#include <type_traits>
+
+#include "test_format_context.h"
+#include "test_macros.h"
+#include "make_string.h"
+#include "assert_macros.h"
+#include "concat_macros.h"
+
+#define STR(S) MAKE_STRING(CharT, S)
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+template <class StringT, class StringViewT, class ArgumentT>
+void test(StringT expected, StringViewT fmt, ArgumentT arg) {
+  using CharT    = typename StringT::value_type;
+  auto parse_ctx = std::basic_format_parse_context<CharT>(fmt);
+  std::formatter<ArgumentT, CharT> formatter;
+  static_assert(std::semiregular<decltype(formatter)>);
+
+  formatter.parse(parse_ctx);
+
+  StringT result;
+  auto out         = std::back_inserter(result);
+  using FormatCtxT = std::basic_format_context<decltype(out), CharT>;
+
+  FormatCtxT format_ctx = test_format_context_create<decltype(out), CharT>(out, std::make_format_args<FormatCtxT>(arg));
+  formatter.format(arg, format_ctx);
+  TEST_REQUIRE(result == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", result, '\n'));
+}
+
+template <class CharT>
+void test() {
+  test(STR("\x00"), STR("}"), '\x00');
+  test(STR("a"), STR("}"), 'a');
+  test(STR("\x80"), STR("}"), '\x80');
+  test(STR("\xff"), STR("}"), '\xff');
+
+  test(STR("\x00"), STR("c}"), '\x00');
+  test(STR("a"), STR("c}"), 'a');
+  test(STR("\x80"), STR("c}"), '\x80');
+  test(STR("\xff"), STR("c}"), '\xff');
+
+#if TEST_STD_VER > 20
+  test(STR(R"('\u{0}')"), STR("?}"), '\x00');
+  test(STR("'a'"), STR("?}"), 'a');
+  if constexpr (std::same_as<CharT, char>) {
+    test(STR(R"('\x{80}')"), STR("?}"), '\x80');
+    test(STR(R"('\x{ff}')"), STR("?}"), '\xff');
+  }
+#  ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  else {
+    test(STR(R"('\u{80}')"), STR("?}"), '\x80');
+    test(STR("'\u00ff'"), STR("?}"), '\xff');
+  }
+#  endif // TEST_HAS_NO_WIDE_CHARACTERS
+#endif   // TEST_STD_VER > 20
+
+  test(STR("10000000"), STR("b}"), char(-128));
+  test(STR("11111111"), STR("b}"), char(-1));
+  test(STR("0"), STR("b}"), char(0));
+  test(STR("1"), STR("b}"), char(1));
+  test(STR("1111111"), STR("b}"), char(127));
+
+  test(STR("10000000"), STR("B}"), char(-128));
+  test(STR("11111111"), STR("B}"), char(-1));
+  test(STR("0"), STR("B}"), char(0));
+  test(STR("1"), STR("B}"), char(1));
+  test(STR("1111111"), STR("B}"), char(127));
+
+  test(STR("128"), STR("d}"), char(-128));
+  test(STR("255"), STR("d}"), char(-1));
+  test(STR("0"), STR("d}"), char(0));
+  test(STR("1"), STR("d}"), char(1));
+  test(STR("127"), STR("d}"), char(127));
+
+  test(STR("200"), STR("o}"), char(-128));
+  test(STR("377"), STR("o}"), char(-1));
+  test(STR("0"), STR("o}"), char(0));
+  test(STR("1"), STR("o}"), char(1));
+  test(STR("177"), STR("o}"), char(127));
+
+  test(STR("80"), STR("x}"), char(-128));
+  test(STR("ff"), STR("x}"), char(-1));
+  test(STR("0"), STR("x}"), char(0));
+  test(STR("1"), STR("x}"), char(1));
+  test(STR("7f"), STR("x}"), char(127));
+
+  test(STR("80"), STR("X}"), char(-128));
+  test(STR("FF"), STR("X}"), char(-1));
+  test(STR("0"), STR("X}"), char(0));
+  test(STR("1"), STR("X}"), char(1));
+  test(STR("7F"), STR("X}"), char(127));
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.funsigned-char.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.funsigned-char.pass.cpp
new file mode 100644
index 000000000000000..c0044cd1f615fda
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.funsigned-char.pass.cpp
@@ -0,0 +1,131 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -funsigned-char
+
+// <format>
+
+// C++23 the formatter is a debug-enabled specialization.
+// [format.formatter.spec]:
+// Each header that declares the template `formatter` provides the following
+// enabled specializations:
+// The specializations
+//   template<> struct formatter<char, char>;
+//   template<> struct formatter<char, wchar_t>;
+//   template<> struct formatter<wchar_t, wchar_t>;
+
+// P2909R4 "Fix formatting of code units as integers (Dude, where’s my char?)"
+// changed the behaviour of char (and wchar_t) when their underlying type is signed.
+
+#include <format>
+#include <cassert>
+#include <concepts>
+#include <iterator>
+#include <type_traits>
+
+#include "test_format_context.h"
+#include "test_macros.h"
+#include "make_string.h"
+#include "assert_m...
[truncated]

Copy link

github-actions bot commented Nov 25, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Member Author

@mordante mordante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM -- please apply the clang-format diff to appease it though.

@mordante
Copy link
Member Author

LGTM -- please apply the clang-format diff to appease it though.

Thanks for the review. It seems I locally have an older clang-format in my script.

@mordante mordante merged commit 16b8c96 into llvm:main Nov 29, 2023
@mordante mordante deleted the GH-dude_where_s_my_char branch November 29, 2023 16:55
Guzhu-AMD pushed a commit to GPUOpen-Drivers/llvm-project that referenced this pull request Nov 30, 2023
Local branch amd-gfx eb25675 Merged main:738c3ede315a into amd-gfx:2fa5a20ffe88
Remote branch main 16b8c96 [libc++][format] Fixes formatting code units as integers. (llvm#73396)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants