[libcxx-commits] [libcxx] 49e736d - [libc++][format] Adds char formatter.

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Thu Oct 7 08:16:03 PDT 2021


Author: Mark de Wever
Date: 2021-10-07T17:15:58+02:00
New Revision: 49e736d845d8e1beb3db0abcc782c9525e44f31e

URL: https://github.com/llvm/llvm-project/commit/49e736d845d8e1beb3db0abcc782c9525e44f31e
DIFF: https://github.com/llvm/llvm-project/commit/49e736d845d8e1beb3db0abcc782c9525e44f31e.diff

LOG: [libc++][format] Adds char formatter.

Implements the formatter for all fundamental integer types.
[format.formatter.spec]/2.1
The specializations
```
  template<> struct formatter<char, char>;
  template<> struct formatter<char, wchar_t>;
  template<> struct formatter<wchar_t, wchar_t>;
```
This removes the stub implemented in D96664.

Implements parts of:
- P0645 Text Formatting

Reviewed By: #libc, ldionne

Differential Revision: https://reviews.llvm.org/D103466

Added: 
    libcxx/include/__format/formatter_char.h
    libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_char.module.verify.cpp
    libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp

Modified: 
    libcxx/include/CMakeLists.txt
    libcxx/include/format
    libcxx/include/module.modulemap
    libcxx/test/std/utilities/format/format.functions/format_tests.h

Removed: 
    


################################################################################
diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 3c6b4c62eec33..568f06bda4812 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -140,6 +140,7 @@ set(files
   __format/format_parse_context.h
   __format/format_string.h
   __format/formatter.h
+  __format/formatter_char.h
   __format/formatter_integer.h
   __format/formatter_integral.h
   __format/formatter_string.h

diff  --git a/libcxx/include/__format/formatter_char.h b/libcxx/include/__format/formatter_char.h
new file mode 100644
index 0000000000000..bbc54c4168099
--- /dev/null
+++ b/libcxx/include/__format/formatter_char.h
@@ -0,0 +1,103 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___FORMAT_FORMATTER_CHAR_H
+#define _LIBCPP___FORMAT_FORMATTER_CHAR_H
+
+#include <__availability>
+#include <__config>
+#include <__format/format_error.h>
+#include <__format/format_fwd.h>
+#include <__format/formatter.h>
+#include <__format/formatter_integral.h>
+#include <__format/parser_std_format_spec.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER > 17
+
+// TODO FMT Remove this once we require compilers with proper C++20 support.
+// If the compiler has no concepts support, the format header will be disabled.
+// Without concepts support enable_if needs to be used and that too much effort
+// to support compilers with partial C++20 support.
+#if !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+namespace __format_spec {
+
+template <class _CharT>
+class _LIBCPP_TEMPLATE_VIS __parser_char : public __parser_integral<_CharT> {
+public:
+  _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx)
+      -> decltype(__parse_ctx.begin()) {
+    auto __it = __parser_integral<_CharT>::__parse(__parse_ctx);
+
+    switch (this->__type) {
+    case _Flags::_Type::__default:
+      this->__type = _Flags::_Type::__char;
+      [[fallthrough]];
+    case _Flags::_Type::__char:
+      this->__handle_char();
+      break;
+
+    case _Flags::_Type::__binary_lower_case:
+    case _Flags::_Type::__binary_upper_case:
+    case _Flags::_Type::__octal:
+    case _Flags::_Type::__decimal:
+    case _Flags::_Type::__hexadecimal_lower_case:
+    case _Flags::_Type::__hexadecimal_upper_case:
+      this->__handle_integer();
+      break;
+
+    default:
+      __throw_format_error(
+          "The format-spec type has a type not supported for a char argument");
+    }
+
+    return __it;
+  }
+};
+
+template <class _CharT>
+using __formatter_char = __formatter_integral<__parser_char<_CharT>>;
+
+} // namespace __format_spec
+
+// [format.formatter.spec]/2.1 The specializations
+
+template <>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<char, char>
+    : public __format_spec::__formatter_char<char> {};
+
+template <>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<char, wchar_t>
+    : public __format_spec::__formatter_char<wchar_t> {
+  using _Base = __format_spec::__formatter_char<wchar_t>;
+
+  _LIBCPP_HIDE_FROM_ABI auto format(char __value, auto& __ctx)
+      -> decltype(__ctx.out()) {
+    return _Base::format(static_cast<wchar_t>(__value), __ctx);
+  }
+};
+
+template <>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+    formatter<wchar_t, wchar_t>
+    : public __format_spec::__formatter_char<wchar_t> {};
+
+#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+#endif //_LIBCPP_STD_VER > 17
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___FORMAT_FORMATTER_CHAR_H

diff  --git a/libcxx/include/format b/libcxx/include/format
index 6e779fe7a714c..d4e24b6c30fcf 100644
--- a/libcxx/include/format
+++ b/libcxx/include/format
@@ -279,6 +279,7 @@ namespace std {
 #include <__format/format_parse_context.h>
 #include <__format/format_string.h>
 #include <__format/formatter.h>
+#include <__format/formatter_char.h>
 #include <__format/formatter_integer.h>
 #include <__format/formatter_string.h>
 #include <__format/parser_std_format_spec.h>
@@ -338,24 +339,7 @@ _LIBCPP_HIDE_FROM_ABI __format_arg_store<wformat_context, _Args...>
 make_wformat_args(const _Args&... __args) {
   return _VSTD::make_format_args<wformat_context>(__args...);
 }
-
 namespace __format {
-template <class _Tp, class _CharT>
-struct _LIBCPP_TEMPLATE_VIS __formatter_char {
-  _LIBCPP_HIDE_FROM_ABI
-  auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) {
-    // TODO FMT Implement this function.
-    return __parse_ctx.begin();
-  }
-
-  _LIBCPP_HIDE_FROM_ABI
-  auto format(_Tp __c, auto& __ctx) -> decltype(__ctx.out()) {
-    // TODO FMT Implement the parsed formatting arguments.
-    auto __out_it = __ctx.out();
-    *__out_it++ = _CharT(__c);
-    return __out_it;
-  }
-};
 
 template <class _Tp, class _CharT>
 requires(is_arithmetic_v<_Tp> &&
@@ -405,20 +389,6 @@ private:
 // These specializations are helper stubs and not proper formatters.
 // TODO FMT Implement the proper formatter specializations.
 
-// [format.formatter.spec]/2.1 The specializations
-
-template <>
-struct _LIBCPP_TEMPLATE_VIS formatter<char, char>
-    : public __format::__formatter_char<char, char> {};
-
-template <>
-struct _LIBCPP_TEMPLATE_VIS formatter<char, wchar_t>
-    : public __format::__formatter_char<char, wchar_t> {};
-
-template <>
-struct _LIBCPP_TEMPLATE_VIS formatter<wchar_t, wchar_t>
-    : public __format::__formatter_char<wchar_t, wchar_t> {};
-
 // [format.formatter.spec]/2.3
 // For each charT, for each cv-unqualified arithmetic type ArithmeticT other
 // than char, wchar_t, char8_t, char16_t, or char32_t, a specialization

diff  --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index f7c3058ede8fd..4f107738313fe 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -450,6 +450,7 @@ module std [system] {
       module format_parse_context   { private header "__format/format_parse_context.h"   }
       module format_string          { private header "__format/format_string.h"          }
       module formatter              { private header "__format/formatter.h"              }
+      module formatter_char         { private header "__format/formatter_char.h"         }
       module formatter_integer      { private header "__format/formatter_integer.h"      }
       module formatter_integral     { private header "__format/formatter_integral.h"     }
       module formatter_string       { private header "__format/formatter_string.h"       }

diff  --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_char.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_char.module.verify.cpp
new file mode 100644
index 0000000000000..14f631df85efb
--- /dev/null
+++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_char.module.verify.cpp
@@ -0,0 +1,16 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: modules-build
+
+// WARNING: This test was generated by 'generate_private_header_tests.py'
+// and should not be edited manually.
+
+// expected-error@*:* {{use of private header from outside its module: '__format/formatter_char.h'}}
+#include <__format/formatter_char.h>

diff  --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp
new file mode 100644
index 0000000000000..07c5bbb815c4f
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_char.pass.cpp
@@ -0,0 +1,452 @@
+//===----------------------------------------------------------------------===//
+// 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
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// <format>
+
+// Tests the parsing of the format string as specified in [format.string.std].
+// It validates whether the std-format-spec is valid for a char type.
+
+#include <format>
+#include <cassert>
+#ifndef _LIBCPP_HAS_NO_LOCALIZATION
+# include <iostream>
+#endif
+
+#include "concepts_precision.h"
+#include "test_macros.h"
+#include "make_string.h"
+#include "test_exception.h"
+
+#define CSTR(S) MAKE_CSTRING(CharT, S)
+
+using namespace std::__format_spec;
+
+template <class CharT>
+using Parser = __parser_char<CharT>;
+
+template <class CharT>
+struct Expected {
+  CharT fill = CharT(' ');
+  _Flags::_Alignment alignment = _Flags::_Alignment::__left;
+  _Flags::_Sign sign = _Flags::_Sign::__default;
+  bool alternate_form = false;
+  bool zero_padding = false;
+  uint32_t width = 0;
+  bool width_as_arg = false;
+  bool locale_specific_form = false;
+  _Flags::_Type type = _Flags::_Type::__char;
+};
+
+template <class CharT>
+constexpr void test(Expected<CharT> expected, size_t size,
+                    std::basic_string_view<CharT> fmt) {
+  // Initialize parser with sufficient arguments to avoid the parsing to fail
+  // due to insufficient arguments.
+  std::basic_format_parse_context<CharT> parse_ctx(fmt,
+                                                   std::__format::__number_max);
+  auto begin = parse_ctx.begin();
+  auto end = parse_ctx.end();
+  Parser<CharT> parser;
+  auto it = parser.parse(parse_ctx);
+
+  assert(begin == parse_ctx.begin());
+  assert(end == parse_ctx.end());
+
+  assert(begin + size == it);
+  assert(parser.__fill == expected.fill);
+  assert(parser.__alignment == expected.alignment);
+  assert(parser.__sign == expected.sign);
+  assert(parser.__alternate_form == expected.alternate_form);
+  assert(parser.__zero_padding == expected.zero_padding);
+  assert(parser.__width == expected.width);
+  assert(parser.__width_as_arg == expected.width_as_arg);
+  assert(parser.__locale_specific_form == expected.locale_specific_form);
+  assert(parser.__type == expected.type);
+}
+
+template <class CharT>
+constexpr void test(Expected<CharT> expected, size_t size, const CharT* f) {
+  // The format-spec is valid if completely consumed or terminates at a '}'.
+  // The valid inputs all end with a '}'. The test is executed twice:
+  // - first with the terminating '}',
+  // - second consuming the entire input.
+  std::basic_string_view<CharT> fmt{f};
+  assert(fmt.back() == CharT('}') && "Pre-condition failure");
+
+  test(expected, size, fmt);
+  fmt.remove_suffix(1);
+  test(expected, size, fmt);
+}
+
+template <class CharT>
+constexpr void test_as_char() {
+
+  test({}, 1, CSTR("c}"));
+
+  // *** Align-fill ***
+  test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}"));
+  test({.alignment = _Flags::_Alignment::__center}, 1, "^}");
+  test({.alignment = _Flags::_Alignment::__right}, 1, ">}");
+
+  test({.alignment = _Flags::_Alignment::__left}, 2, CSTR("<c}"));
+  test({.alignment = _Flags::_Alignment::__center}, 2, "^c}");
+  test({.alignment = _Flags::_Alignment::__right}, 2, ">c}");
+
+  test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 2,
+       CSTR("L<}"));
+  test({.fill = CharT('#'), .alignment = _Flags::_Alignment::__center}, 2,
+       CSTR("#^}"));
+  test({.fill = CharT('0'), .alignment = _Flags::_Alignment::__right}, 2,
+       CSTR("0>}"));
+
+  test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 3,
+       CSTR("L<c}"));
+  test({.fill = CharT('#'), .alignment = _Flags::_Alignment::__center}, 3,
+       CSTR("#^c}"));
+  test({.fill = CharT('0'), .alignment = _Flags::_Alignment::__right}, 3,
+       CSTR("0>c}"));
+
+  // *** Sign ***
+  test_exception<Parser<CharT>>(
+      "A sign field isn't allowed in this format-spec", CSTR("-"));
+  test_exception<Parser<CharT>>(
+      "A sign field isn't allowed in this format-spec", CSTR("+"));
+  test_exception<Parser<CharT>>(
+      "A sign field isn't allowed in this format-spec", CSTR(" "));
+
+  test_exception<Parser<CharT>>(
+      "A sign field isn't allowed in this format-spec", CSTR("-c"));
+  test_exception<Parser<CharT>>(
+      "A sign field isn't allowed in this format-spec", CSTR("+c"));
+  test_exception<Parser<CharT>>(
+      "A sign field isn't allowed in this format-spec", CSTR(" c"));
+
+  // *** Alternate form ***
+  test_exception<Parser<CharT>>(
+      "An alternate form field isn't allowed in this format-spec", CSTR("#}"));
+  test_exception<Parser<CharT>>(
+      "An alternate form field isn't allowed in this format-spec", CSTR("#c}"));
+
+  // *** Zero padding ***
+  test_exception<Parser<CharT>>(
+      "A zero-padding field isn't allowed in this format-spec", CSTR("0}"));
+  test_exception<Parser<CharT>>(
+      "A zero-padding field isn't allowed in this format-spec", CSTR("0c}"));
+
+  // *** Width ***
+  test({.width = 0, .width_as_arg = false}, 0, CSTR("}"));
+  test({.width = 1, .width_as_arg = false}, 1, CSTR("1}"));
+  test({.width = 10, .width_as_arg = false}, 2, CSTR("10}"));
+  test({.width = 1000, .width_as_arg = false}, 4, CSTR("1000}"));
+  test({.width = 1000000, .width_as_arg = false}, 7, CSTR("1000000}"));
+
+  test({.width = 0, .width_as_arg = true}, 2, CSTR("{}}"));
+  test({.width = 0, .width_as_arg = true}, 3, CSTR("{0}}"));
+  test({.width = 1, .width_as_arg = true}, 3, CSTR("{1}}"));
+
+  test_exception<Parser<CharT>>(
+      "A format-spec width field shouldn't have a leading zero", CSTR("00"));
+
+  static_assert(std::__format::__number_max == 2'147'483'647,
+                "Update the assert and the test.");
+  test({.width = 2'147'483'647, .width_as_arg = false}, 10,
+       CSTR("2147483647}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR("2147483648"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR("5000000000"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR("10000000000"));
+
+  test_exception<Parser<CharT>>("End of input while parsing format-spec arg-id",
+                                CSTR("{"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{0"));
+  test_exception<Parser<CharT>>(
+      "The arg-id of the format-spec starts with an invalid character",
+      CSTR("{a"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{1"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{9"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{9:"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{9a"));
+
+  static_assert(std::__format::__number_max == 2'147'483'647,
+                "Update the assert and the test.");
+  // Note the static_assert tests whether the arg-id is valid.
+  // Therefore the following should be true arg-id < __format::__number_max.
+  test({.width = 2'147'483'646, .width_as_arg = true}, 12,
+       CSTR("{2147483646}}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR("{2147483648}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR("{5000000000}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR("{10000000000}"));
+
+  // *** Precision ***
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("."));
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR(".1"));
+
+  // *** Locale-specific form ***
+  // Note the flag is allowed, but has no effect.
+  test({.locale_specific_form = true}, 1, CSTR("L}"));
+  test({.locale_specific_form = true}, 2, CSTR("Lc}"));
+}
+
+template <class CharT>
+constexpr void test_as_integer() {
+
+  test({.alignment = _Flags::_Alignment::__right,
+        .type = _Flags::_Type::__decimal},
+       1, CSTR("d}"));
+
+  // *** Align-fill ***
+  test({.alignment = _Flags::_Alignment::__left,
+        .type = _Flags::_Type::__decimal},
+       2, CSTR("<d}"));
+  test({.alignment = _Flags::_Alignment::__center,
+        .type = _Flags::_Type::__decimal},
+       2, "^d}");
+  test({.alignment = _Flags::_Alignment::__right,
+        .type = _Flags::_Type::__decimal},
+       2, ">d}");
+
+  test({.fill = CharT('L'),
+        .alignment = _Flags::_Alignment::__left,
+        .type = _Flags::_Type::__decimal},
+       3, CSTR("L<d}"));
+  test({.fill = CharT('#'),
+        .alignment = _Flags::_Alignment::__center,
+        .type = _Flags::_Type::__decimal},
+       3, CSTR("#^d}"));
+  test({.fill = CharT('0'),
+        .alignment = _Flags::_Alignment::__right,
+        .type = _Flags::_Type::__decimal},
+       3, CSTR("0>d}"));
+
+  // *** Sign ***
+  test({.alignment = _Flags::_Alignment::__right,
+        .sign = _Flags::_Sign::__minus,
+        .type = _Flags::_Type::__decimal},
+       2, CSTR("-d}"));
+  test({.alignment = _Flags::_Alignment::__right,
+        .sign = _Flags::_Sign::__plus,
+        .type = _Flags::_Type::__decimal},
+       2, CSTR("+d}"));
+  test({.alignment = _Flags::_Alignment::__right,
+        .sign = _Flags::_Sign::__space,
+        .type = _Flags::_Type::__decimal},
+       2, CSTR(" d}"));
+
+  // *** Alternate form ***
+  test({.alignment = _Flags::_Alignment::__right,
+        .alternate_form = true,
+        .type = _Flags::_Type::__decimal},
+       2, CSTR("#d}"));
+
+  // *** Zero padding ***
+  test({.alignment = _Flags::_Alignment::__default,
+        .zero_padding = true,
+        .type = _Flags::_Type::__decimal},
+       2, CSTR("0d}"));
+  test({.alignment = _Flags::_Alignment::__center,
+        .type = _Flags::_Type::__decimal},
+       3, CSTR("^0d}"));
+
+  // *** Width ***
+  test({.alignment = _Flags::_Alignment::__right,
+        .width = 0,
+        .width_as_arg = false,
+        .type = _Flags::_Type::__decimal},
+       1, CSTR("d}"));
+  test({.alignment = _Flags::_Alignment::__right,
+        .width = 1,
+        .width_as_arg = false,
+        .type = _Flags::_Type::__decimal},
+       2, CSTR("1d}"));
+  test({.alignment = _Flags::_Alignment::__right,
+        .width = 10,
+        .width_as_arg = false,
+        .type = _Flags::_Type::__decimal},
+       3, CSTR("10d}"));
+  test({.alignment = _Flags::_Alignment::__right,
+        .width = 1000,
+        .width_as_arg = false,
+        .type = _Flags::_Type::__decimal},
+       5, CSTR("1000d}"));
+  test({.alignment = _Flags::_Alignment::__right,
+        .width = 1000000,
+        .width_as_arg = false,
+        .type = _Flags::_Type::__decimal},
+       8, CSTR("1000000d}"));
+
+  test({.alignment = _Flags::_Alignment::__right,
+        .width = 0,
+        .width_as_arg = true,
+        .type = _Flags::_Type::__decimal},
+       3, CSTR("{}d}"));
+  test({.alignment = _Flags::_Alignment::__right,
+        .width = 0,
+        .width_as_arg = true,
+        .type = _Flags::_Type::__decimal},
+       4, CSTR("{0}d}"));
+  test({.alignment = _Flags::_Alignment::__right,
+        .width = 1,
+        .width_as_arg = true,
+        .type = _Flags::_Type::__decimal},
+       4, CSTR("{1}d}"));
+
+  // *** Precision ***
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("."));
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR(".1"));
+
+  // *** Locale-specific form ***
+  test({.alignment = _Flags::_Alignment::__right,
+        .locale_specific_form = true,
+        .type = _Flags::_Type::__decimal},
+       2, CSTR("Ld}"));
+}
+
+template <class CharT>
+constexpr void test() {
+  Parser<CharT> parser;
+
+  assert(parser.__fill == CharT(' '));
+  assert(parser.__alignment == _Flags::_Alignment::__default);
+  assert(parser.__sign == _Flags::_Sign::__default);
+  assert(parser.__alternate_form == false);
+  assert(parser.__zero_padding == false);
+  assert(parser.__width == 0);
+  assert(parser.__width_as_arg == false);
+  static_assert(!has_precision<decltype(parser)>);
+  static_assert(!has_precision_as_arg<decltype(parser)>);
+  assert(parser.__locale_specific_form == false);
+  assert(parser.__type == _Flags::_Type::__default);
+
+  test({}, 0, CSTR("}"));
+
+  test_as_char<CharT>();
+  test_as_integer<CharT>();
+
+  // *** Type ***
+  {
+    const char* unsuported_type =
+        "The format-spec type has a type not supported for a char argument";
+    const char* not_a_type =
+        "The format-spec should consume the input or end with a '}'";
+
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("A}"));
+    test({.alignment = _Flags::_Alignment::__right,
+          .type = _Flags::_Type::__binary_upper_case},
+         1, CSTR("B}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("C}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("D}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("E}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("F}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("G}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("H}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("I}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("J}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("K}"));
+    test({.locale_specific_form = true}, 1, CSTR("L}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("M}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("N}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("O}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("P}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("Q}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("R}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("S}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("T}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("U}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("V}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("W}"));
+    test({.alignment = _Flags::_Alignment::__right,
+          .type = _Flags::_Type::__hexadecimal_upper_case},
+         1, CSTR("X}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("Y}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("Z}"));
+
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("a}"));
+    test({.alignment = _Flags::_Alignment::__right,
+          .type = _Flags::_Type::__binary_lower_case},
+         1, CSTR("b}"));
+    test({.type = _Flags::_Type::__char}, 1, CSTR("c}"));
+    test({.alignment = _Flags::_Alignment::__right,
+          .type = _Flags::_Type::__decimal},
+         1, CSTR("d}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("e}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("f}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("g}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("h}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("i}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("j}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("k}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("l}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("m}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("n}"));
+    test({.alignment = _Flags::_Alignment::__right,
+          .type = _Flags::_Type::__octal},
+         1, CSTR("o}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("p}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("q}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("r}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("s}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("t}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("u}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("v}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("w}"));
+    test({.alignment = _Flags::_Alignment::__right,
+          .type = _Flags::_Type::__hexadecimal_lower_case},
+         1, CSTR("x}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("y}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("z}"));
+  }
+
+  // **** General ***
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("ss"));
+}
+
+constexpr bool test() {
+  test<char>();
+  test<wchar_t>();
+
+  return true;
+}
+
+int main(int, char**) {
+#ifndef _WIN32
+  // TODO FMT Investigate why this doesn't work.
+  // (Wait until LWG-3576 has been resolved.)
+  // Make sure the parsers match the expectations. The layout of the
+  // subobjects is chosen to minimize the size required.
+  static_assert(sizeof(Parser<char>) == 2 * sizeof(uint32_t));
+  static_assert(
+      sizeof(Parser<wchar_t>) ==
+      (sizeof(wchar_t) <= 2 ? 2 * sizeof(uint32_t) : 3 * sizeof(uint32_t)));
+#endif
+
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h
index ae186461095c1..1478288b8c976 100644
--- a/libcxx/test/std/utilities/format/format.functions/format_tests.h
+++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h
@@ -587,6 +587,155 @@ void format_test_unsigned_integer(TestFunction check,
   // TODO FMT Add __uint128_t test after implementing full range.
 }
 
+template <class CharT, class TestFunction, class ExceptionTest>
+void format_test_char(TestFunction check, ExceptionTest check_exception) {
+
+  // ***** Char type *****
+  // *** align-fill & width ***
+  check(STR("answer is '*     '"), STR("answer is '{:6}'"), CharT('*'));
+  check(STR("answer is '     *'"), STR("answer is '{:>6}'"), CharT('*'));
+  check(STR("answer is '*     '"), STR("answer is '{:<6}'"), CharT('*'));
+  check(STR("answer is '  *   '"), STR("answer is '{:^6}'"), CharT('*'));
+
+  check(STR("answer is '*     '"), STR("answer is '{:6c}'"), CharT('*'));
+  check(STR("answer is '     *'"), STR("answer is '{:>6c}'"), CharT('*'));
+  check(STR("answer is '*     '"), STR("answer is '{:<6c}'"), CharT('*'));
+  check(STR("answer is '  *   '"), STR("answer is '{:^6c}'"), CharT('*'));
+
+  check(STR("answer is '-----*'"), STR("answer is '{:->6}'"), CharT('*'));
+  check(STR("answer is '*-----'"), STR("answer is '{:-<6}'"), CharT('*'));
+  check(STR("answer is '--*---'"), STR("answer is '{:-^6}'"), CharT('*'));
+
+  check(STR("answer is '-----*'"), STR("answer is '{:->6c}'"), CharT('*'));
+  check(STR("answer is '*-----'"), STR("answer is '{:-<6c}'"), CharT('*'));
+  check(STR("answer is '--*---'"), STR("answer is '{:-^6c}'"), CharT('*'));
+
+  // *** Sign ***
+  check_exception("A sign field isn't allowed in this format-spec", STR("{:-}"),
+                  CharT('*'));
+  check_exception("A sign field isn't allowed in this format-spec", STR("{:+}"),
+                  CharT('*'));
+  check_exception("A sign field isn't allowed in this format-spec", STR("{: }"),
+                  CharT('*'));
+
+  check_exception("A sign field isn't allowed in this format-spec",
+                  STR("{:-c}"), CharT('*'));
+  check_exception("A sign field isn't allowed in this format-spec",
+                  STR("{:+c}"), CharT('*'));
+  check_exception("A sign field isn't allowed in this format-spec",
+                  STR("{: c}"), CharT('*'));
+
+  // *** alternate form ***
+  check_exception("An alternate form field isn't allowed in this format-spec",
+                  STR("{:#}"), CharT('*'));
+  check_exception("An alternate form field isn't allowed in this format-spec",
+                  STR("{:#c}"), CharT('*'));
+
+  // *** zero-padding ***
+  check_exception("A zero-padding field isn't allowed in this format-spec",
+                  STR("{:0}"), CharT('*'));
+  check_exception("A zero-padding field isn't allowed in this format-spec",
+                  STR("{:0c}"), CharT('*'));
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'",
+                  STR("{:.}"), CharT('*'));
+  check_exception("The format-spec should consume the input or end with a '}'",
+                  STR("{:.0}"), CharT('*'));
+  check_exception("The format-spec should consume the input or end with a '}'",
+                  STR("{:.42}"), CharT('*'));
+
+  check_exception("The format-spec should consume the input or end with a '}'",
+                  STR("{:.c}"), CharT('*'));
+  check_exception("The format-spec should consume the input or end with a '}'",
+                  STR("{:.0c}"), CharT('*'));
+  check_exception("The format-spec should consume the input or end with a '}'",
+                  STR("{:.42c}"), CharT('*'));
+
+  // *** locale-specific form ***
+  // Note it has no effect but it's allowed.
+  check(STR("answer is '*'"), STR("answer is '{:L}'"), '*');
+  check(STR("answer is '*'"), STR("answer is '{:Lc}'"), '*');
+
+  // *** type ***
+  for (const auto& fmt : invalid_types<CharT>("bBcdoxX"))
+    check_exception(
+        "The format-spec type has a type not supported for a char argument",
+        fmt, CharT('*'));
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void format_test_char_as_integer(TestFunction check,
+                                 ExceptionTest check_exception) {
+  // *** align-fill & width ***
+  check(STR("answer is '42'"), STR("answer is '{:<1d}'"), CharT('*'));
+
+  check(STR("answer is '42'"), STR("answer is '{:<2d}'"), CharT('*'));
+  check(STR("answer is '42 '"), STR("answer is '{:<3d}'"), CharT('*'));
+
+  check(STR("answer is '     42'"), STR("answer is '{:7d}'"), CharT('*'));
+  check(STR("answer is '     42'"), STR("answer is '{:>7d}'"), CharT('*'));
+  check(STR("answer is '42     '"), STR("answer is '{:<7d}'"), CharT('*'));
+  check(STR("answer is '  42   '"), STR("answer is '{:^7d}'"), CharT('*'));
+
+  check(STR("answer is '*****42'"), STR("answer is '{:*>7d}'"), CharT('*'));
+  check(STR("answer is '42*****'"), STR("answer is '{:*<7d}'"), CharT('*'));
+  check(STR("answer is '**42***'"), STR("answer is '{:*^7d}'"), CharT('*'));
+
+  // Test whether zero padding is ignored
+  check(STR("answer is '     42'"), STR("answer is '{:>07d}'"), CharT('*'));
+  check(STR("answer is '42     '"), STR("answer is '{:<07d}'"), CharT('*'));
+  check(STR("answer is '  42   '"), STR("answer is '{:^07d}'"), CharT('*'));
+
+  // *** Sign ***
+  check(STR("answer is 42"), STR("answer is {:d}"), CharT('*'));
+  check(STR("answer is 42"), STR("answer is {:-d}"), CharT('*'));
+  check(STR("answer is +42"), STR("answer is {:+d}"), CharT('*'));
+  check(STR("answer is  42"), STR("answer is {: d}"), CharT('*'));
+
+  // *** alternate form ***
+  check(STR("answer is +42"), STR("answer is {:+#d}"), CharT('*'));
+  check(STR("answer is +101010"), STR("answer is {:+b}"), CharT('*'));
+  check(STR("answer is +0b101010"), STR("answer is {:+#b}"), CharT('*'));
+  check(STR("answer is +0B101010"), STR("answer is {:+#B}"), CharT('*'));
+  check(STR("answer is +52"), STR("answer is {:+o}"), CharT('*'));
+  check(STR("answer is +052"), STR("answer is {:+#o}"), CharT('*'));
+  check(STR("answer is +2a"), STR("answer is {:+x}"), CharT('*'));
+  check(STR("answer is +0x2a"), STR("answer is {:+#x}"), CharT('*'));
+  check(STR("answer is +2A"), STR("answer is {:+X}"), CharT('*'));
+  check(STR("answer is +0X2A"), STR("answer is {:+#X}"), CharT('*'));
+
+  // *** zero-padding & width ***
+  check(STR("answer is +00000000042"), STR("answer is {:+#012d}"), CharT('*'));
+  check(STR("answer is +00000101010"), STR("answer is {:+012b}"), CharT('*'));
+  check(STR("answer is +0b000101010"), STR("answer is {:+#012b}"), CharT('*'));
+  check(STR("answer is +0B000101010"), STR("answer is {:+#012B}"), CharT('*'));
+  check(STR("answer is +00000000052"), STR("answer is {:+012o}"), CharT('*'));
+  check(STR("answer is +00000000052"), STR("answer is {:+#012o}"), CharT('*'));
+  check(STR("answer is +0000000002a"), STR("answer is {:+012x}"), CharT('*'));
+  check(STR("answer is +0x00000002a"), STR("answer is {:+#012x}"), CharT('*'));
+  check(STR("answer is +0000000002A"), STR("answer is {:+012X}"), CharT('*'));
+
+  check(STR("answer is +0X00000002A"), STR("answer is {:+#012X}"), CharT('*'));
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'",
+                  STR("{:.d}"), CharT('*'));
+  check_exception("The format-spec should consume the input or end with a '}'",
+                  STR("{:.0d}"), CharT('*'));
+  check_exception("The format-spec should consume the input or end with a '}'",
+                  STR("{:.42d}"), CharT('*'));
+
+  // *** locale-specific form ***
+  // See locale-specific_form.pass.cpp
+
+  // *** type ***
+  for (const auto& fmt : invalid_types<CharT>("bBcdoxX"))
+    check_exception(
+        "The format-spec type has a type not supported for a char argument",
+        fmt, '*');
+}
+
 template <class CharT, class TestFunction, class ExceptionTest>
 void format_tests(TestFunction check, ExceptionTest check_exception) {
   // *** Test escaping  ***
@@ -622,6 +771,9 @@ void format_tests(TestFunction check, ExceptionTest check_exception) {
   check(STR("hello 09azAZ!"), STR("hello {}{}{}{}{}{}{}"), CharT('0'),
         CharT('9'), CharT('a'), CharT('z'), CharT('A'), CharT('Z'), CharT('!'));
 
+  format_test_char<CharT>(check, check_exception);
+  format_test_char_as_integer<CharT>(check, check_exception);
+
   // *** Test string format argument ***
   {
     CharT buffer[] = {CharT('0'), CharT('9'), CharT('a'), CharT('z'),


        


More information about the libcxx-commits mailing list