[libcxx-commits] [libcxx] [libc++][format] Fixes formatting code units as integers. (PR #73396)

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Tue Nov 28 10:29:14 PST 2023


https://github.com/mordante updated https://github.com/llvm/llvm-project/pull/73396

>From bde72ecf152059e454f82f9c4c1a5d846d3f5a53 Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Sat, 25 Nov 2023 16:53:54 +0100
Subject: [PATCH 1/2] [libc++][format] Fixes formatting code units as integers.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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``?)
---
 libcxx/docs/FeatureTestMacroTable.rst         |   2 +-
 libcxx/docs/ReleaseNotes/18.rst               |   1 +
 libcxx/docs/Status/Cxx2cPapers.csv            |   2 +-
 libcxx/docs/Status/FormatIssues.csv           |   2 +-
 libcxx/include/__format/format_arg_store.h    |  10 +-
 libcxx/include/__format/formatter_char.h      |  19 ++-
 libcxx/include/version                        |   2 +-
 .../.version.compile.pass.cpp                 |  48 ++-----
 .../version.version.compile.pass.cpp          |  48 ++-----
 .../formatter.char.fsigned-char.pass.cpp      | 131 ++++++++++++++++++
 .../formatter.char.funsigned-char.pass.cpp    | 131 ++++++++++++++++++
 .../generate_feature_test_macro_components.py |   1 -
 12 files changed, 312 insertions(+), 85 deletions(-)
 create mode 100644 libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.fsigned-char.pass.cpp
 create mode 100644 libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char.funsigned-char.pass.cpp

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_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(255));
+  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(255));
+  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(255));
+  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(255));
+  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(255));
+  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(255));
+  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/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 3a068ab120b1599..47ee20de0fdc2b2 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -487,7 +487,6 @@ def add_version_header(tc):
                 "c++20": 202311  # DR P2909R4 Fix formatting of code units as integers
             },
             "headers": [""],  # Note not in format
-            "unimplemented": True,
         },
         {
             "name": "__cpp_lib_formatters",

>From cba5ac688d1f1a1e24648eba5f0268ba33bf82b3 Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Tue, 28 Nov 2023 19:28:58 +0100
Subject: [PATCH 2/2] Addresses review comments.

---
 .../format.formatter.spec/formatter.char.fsigned-char.pass.cpp   | 1 -
 .../format.formatter.spec/formatter.char.funsigned-char.pass.cpp | 1 -
 2 files changed, 2 deletions(-)

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
index 933cb1ff962bb83..d0da8d69f3f9982 100644
--- 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
@@ -14,7 +14,6 @@
 // [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>;
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
index c0044cd1f615fda..9c31ecad85eacfd 100644
--- 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
@@ -14,7 +14,6 @@
 // [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>;



More information about the libcxx-commits mailing list