[libcxx-commits] [libcxx] ed8ea2b - [libc++][format] range-default-formatter for strings.

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Sun Apr 9 03:48:53 PDT 2023


Author: Mark de Wever
Date: 2023-04-09T12:48:15+02:00
New Revision: ed8ea2bbf84388b60db5a634870baa4c26001d73

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

LOG: [libc++][format] range-default-formatter for strings.

Implements the range-default-formatter specialization range_format::string
and range_format::debug_string.

Implements parts of
- P2286R8 Formatting Ranges
- P2585R0 Improving default container formatting

Depends on D145847

Reviewed By: ldionne, #libc

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

Added: 
    libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.format.pass.cpp
    libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.tests.h
    libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.vformat.pass.cpp
    libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.pass.cpp
    libcxx/test/std/utilities/format/format.range/format.range.fmtstr/parse.pass.cpp

Modified: 
    libcxx/docs/Status/FormatPaper.csv
    libcxx/include/__format/range_default_formatter.h

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv
index 6661feeeb17af..19e8f036be852 100644
--- a/libcxx/docs/Status/FormatPaper.csv
+++ b/libcxx/docs/Status/FormatPaper.csv
@@ -37,8 +37,8 @@ Section,Description,Dependencies,Assignee,Status,First released version
 `[format.range] <https://wg21.link/format.range>`_,"Formatting for ranges: ``pair`` and ``tuple``",,Mark de Wever,|Complete|,Clang 16
 `[format.range] <https://wg21.link/format.range>`_,"Formatting for ranges: ``vector<bool>``",,Mark de Wever,|Complete|,Clang 16
 "`P2585R0 <https://wg21.link/P2585R0>`__","Improving default container formatting"
-`[format.range.fmtstr] <https://wg21.link/format.range.fmtstr>`_,"Formatting for ranges: strings",,Mark de Wever,|In Progress|,
-`[format.range.fmtstr] <https://wg21.link/format.range.fmtstr>`_,"Formatting for ranges: strings",,Mark de Wever,|In Progress|,
+`[format.range.fmtstr] <https://wg21.link/format.range.fmtstr>`_,"Formatting for ranges: strings",,Mark de Wever,|Complete|,Clang 17
+`[format.range.fmtstr] <https://wg21.link/format.range.fmtstr>`_,"Formatting for ranges: debug_strings",,Mark de Wever,|Complete|,Clang 17
 "`P2693R1 <https://wg21.link/P2693R1>`__","Formatting ``thread::id`` and ``stacktrace``"
 `[thread.thread.id] <https://wg21.link/thread.thread.id>`_,"Formatting ``thread::id``",,Mark de Wever,|Complete|,Clang 17
 `[stacktrace.format] <https://wg21.link/stacktrace.format>`_,"Formatting ``stacktrace``",A ``<stacktrace>`` implementation,Mark de Wever,,

diff  --git a/libcxx/include/__format/range_default_formatter.h b/libcxx/include/__format/range_default_formatter.h
index eab2951fcf552..3954f98c967e6 100644
--- a/libcxx/include/__format/range_default_formatter.h
+++ b/libcxx/include/__format/range_default_formatter.h
@@ -14,6 +14,7 @@
 #  pragma GCC system_header
 #endif
 
+#include <__algorithm/ranges_copy.h>
 #include <__availability>
 #include <__chrono/statically_widen.h>
 #include <__concepts/same_as.h>
@@ -21,7 +22,10 @@
 #include <__format/concepts.h>
 #include <__format/formatter.h>
 #include <__format/range_formatter.h>
+#include <__iterator/back_insert_iterator.h>
 #include <__ranges/concepts.h>
+#include <__ranges/data.h>
+#include <__ranges/size.h>
 #include <__type_traits/remove_cvref.h>
 #include <__utility/pair.h>
 #include <string_view>
@@ -169,7 +173,40 @@ struct _LIBCPP_TEMPLATE_VIS __range_default_formatter<range_format::set, _Rp, _C
 template <range_format _Kp, ranges::input_range _Rp, class _CharT>
   requires(_Kp == range_format::string || _Kp == range_format::debug_string)
 struct _LIBCPP_TEMPLATE_VIS __range_default_formatter<_Kp, _Rp, _CharT> {
-  __range_default_formatter() = delete; // TODO FMT Implement
+private:
+  // This deviates from the Standard, there the exposition only type is
+  //   formatter<basic_string<charT>, charT> underlying_;
+  // Using a string_view allows the format function to avoid a copy of the
+  // input range when it is a contigious range.
+  formatter<basic_string_view<_CharT>, _CharT> __underlying_;
+
+public:
+  template <class _ParseContext>
+  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
+    typename _ParseContext::iterator __i = __underlying_.parse(__ctx);
+    if constexpr (_Kp == range_format::debug_string)
+      __underlying_.set_debug_format();
+    return __i;
+  }
+
+  template <class _FormatContext>
+  _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator
+  format(conditional_t<ranges::input_range<const _Rp>, const _Rp&, _Rp&> __range, _FormatContext& __ctx) const {
+    // When the range is contiguous use a basic_string_view instead to avoid a
+    // copy of the underlying data. The basic_string_view formatter
+    // specialization is the "basic" string formatter in libc++.
+    if constexpr (ranges::contiguous_range<_Rp> && std::ranges::sized_range<_Rp>)
+      return __underlying_.format(basic_string_view<_CharT>{ranges::data(__range), ranges::size(__range)}, __ctx);
+    else {
+      // P2106's from_range has not been implemented yet. Instead use a simple
+      // copy operation.
+      // TODO FMT use basic_string's "from_range" constructor.
+      // return __underlying_.format(basic_string<_CharT>{from_range, __range}, __ctx);
+      basic_string<_CharT> __str;
+      std::ranges::copy(__range, back_insert_iterator{__str});
+      return __underlying_.format(static_cast<basic_string_view<_CharT>>(__str), __ctx);
+    }
+  }
 };
 
 template <ranges::input_range _Rp, class _CharT>

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.format.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.format.pass.cpp
new file mode 100644
index 0000000000000..2de23768a5f3f
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.format.pass.cpp
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+// 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, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// XFAIL: availability-fp_to_chars-missing
+
+// <format>
+
+// template<ranges::input_range R, class charT>
+//   requires (K == range_format::string || K == range_format::debug_string)
+// struct range-default-formatter<K, R, charT>
+//
+// template<class... Args>
+//   string format(format_string<Args...> fmt, Args&&... args);
+// template<class... Args>
+//   wstring format(wformat_string<Args...> fmt, Args&&... args);
+
+#include <format>
+#include <cassert>
+
+#include "format.functions.tests.h"
+#include "test_format_string.h"
+#include "test_macros.h"
+#include "assert_macros.h"
+#include "concat_macros.h"
+
+auto test = []<class CharT, class... Args>(
+                std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) {
+  std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception = []<class CharT, class... Args>(std::string_view, std::basic_string_view<CharT>, Args&&...) {
+  // After P2216 most exceptions thrown by std::format become ill-formed.
+  // Therefore this tests does nothing.
+};
+
+int main(int, char**) {
+  format_tests<char>(test, test_exception);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  format_tests<wchar_t>(test, test_exception);
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.tests.h b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.tests.h
new file mode 100644
index 0000000000000..c0e9c9dc6c393
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.tests.h
@@ -0,0 +1,439 @@
+//===----------------------------------------------------------------------===//
+// 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 TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FMTSTR_FORMAT_FUNCTIONS_TESTS_H
+#define TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FMTSTR_FORMAT_FUNCTIONS_TESTS_H
+
+#include <array>
+#include <format>
+#include <list>
+
+#include "format.functions.common.h"
+#include "make_string.h"
+#include "platform_support.h" // locale name macros
+#include "test_macros.h"
+
+//
+// Types
+//
+
+template <class Container>
+class test_range_format_string {
+public:
+  explicit test_range_format_string(Container str) : str_(std::move(str)) {}
+
+  typename Container::const_iterator begin() const { return str_.begin(); }
+  typename Container::const_iterator end() const { return str_.end(); }
+
+private:
+  Container str_;
+};
+
+template <class Container>
+constexpr std::range_format std::format_kind<test_range_format_string<Container>> = std::range_format::string;
+
+template <class Container>
+class test_range_format_debug_string {
+public:
+  explicit test_range_format_debug_string(Container str) : str_(std::move(str)) {}
+
+  typename Container::const_iterator begin() const { return str_.begin(); }
+  typename Container::const_iterator end() const { return str_.end(); }
+
+private:
+  Container str_;
+};
+
+template <class Container>
+constexpr std::range_format std::format_kind<test_range_format_debug_string<Container>> =
+    std::range_format::debug_string;
+
+//
+// String
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  check(SV("hello"), SV("{}"), input);
+
+  // *** align-fill & width ***
+  check(SV("hello     "), SV("{:10}"), input);
+  check(SV("hello*****"), SV("{:*<10}"), input);
+  check(SV("__hello___"), SV("{:_^10}"), input);
+  check(SV(":::::hello"), SV("{::>10}"), input);
+
+  check(SV("hello     "), SV("{:{}}"), input, 10);
+  check(SV("hello*****"), SV("{:*<{}}"), input, 10);
+  check(SV("__hello___"), SV("{:_^{}}"), input, 10);
+  check(SV(":::::hello"), SV("{::>{}}"), input, 10);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{:{<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check(SV("hel"), SV("{:.3}"), input);
+  check(SV("hel"), SV("{:.{}}"), input, 3);
+
+  check(SV("hel  "), SV("{:5.3}"), input);
+  check(SV("hel  "), SV("{:{}.{}}"), input, 5, 3);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** type ***
+  check(SV("hello"), SV("{:s}"), input);
+  check(SV("\"hello\""), SV("{:?}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s?"))
+    check_exception("The format-spec type has a type not supported for a string argument", fmt, input);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_string(TestFunction check, ExceptionTest check_exception) {
+  // libc++ uses 
diff erent containers for contiguous and non-contiguous ranges.
+  std::basic_string<CharT> input = STR("hello");
+  test_string<CharT>(check, check_exception, test_range_format_string<std::basic_string<CharT>>{input});
+  test_string<CharT>(check, check_exception, test_range_format_string<std::basic_string_view<CharT>>{input});
+  test_string<CharT>(
+      check, check_exception, test_range_format_string<std::list<CharT>>{std::list<CharT>{input.begin(), input.end()}});
+}
+
+//
+// String range
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_range_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  check(SV(R"([Hello, world])"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV(R"([Hello, world]     )"), SV("{:19}"), input);
+  check(SV(R"([Hello, world]*****)"), SV("{:*<19}"), input);
+  check(SV(R"(__[Hello, world]___)"), SV("{:_^19}"), input);
+  check(SV(R"(#####[Hello, world])"), SV("{:#>19}"), input);
+
+  check(SV(R"([Hello, world]     )"), SV("{:{}}"), input, 19);
+  check(SV(R"([Hello, world]*****)"), SV("{:*<{}}"), input, 19);
+  check(SV(R"(__[Hello, world]___)"), SV("{:_^{}}"), input, 19);
+  check(SV(R"(#####[Hello, world])"), SV("{:#>{}}"), input, 19);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV(R"(_Hello, world_)"), SV("{:_^14n}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV(R"([Hello   , world   ])"), SV("{::8}"), input);
+  check(SV(R"([Hello***, world***])"), SV("{::*<8}"), input);
+  check(SV(R"([_Hello__, _world__])"), SV("{::_^8}"), input);
+  check(SV(R"([:::Hello, :::world])"), SV("{:::>8}"), input);
+
+  check(SV(R"([Hello   , world   ])"), SV("{::{}}"), input, 8);
+  check(SV(R"([Hello***, world***])"), SV("{::*<{}}"), input, 8);
+  check(SV(R"([_Hello__, _world__])"), SV("{::_^{}}"), input, 8);
+  check(SV(R"([:::Hello, :::world])"), SV("{:::>{}}"), input, 8);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::-}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{::05}"), input);
+
+  // *** precision ***
+  check(SV(R"([Hel, wor])"), SV("{::.3}"), input);
+
+  check(SV(R"([Hel, wor])"), SV("{::.{}}"), input, 3);
+
+  check_exception("The format-spec precision field doesn't contain a value or arg-id", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::L}"), input);
+
+  // *** type ***
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("s?"))
+    check_exception("The format-spec type has a type not supported for a string argument", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input);
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25);
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8);
+
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input);
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25);
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}::>8}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 25);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_range_string(TestFunction check, ExceptionTest check_exception) {
+  // libc++ uses 
diff erent containers for contiguous and non-contiguous ranges.
+  std::array input{STR("Hello"), STR("world")};
+  test_range_string<CharT>(
+      check,
+      check_exception,
+      std::array{test_range_format_string<std::basic_string<CharT>>{input[0]},
+                 test_range_format_string<std::basic_string<CharT>>{input[1]}});
+  test_range_string<CharT>(
+      check,
+      check_exception,
+      std::array{test_range_format_string<std::basic_string_view<CharT>>{input[0]},
+                 test_range_format_string<std::basic_string_view<CharT>>{input[1]}});
+  test_range_string<CharT>(
+      check,
+      check_exception,
+      std::array{test_range_format_string<std::list<CharT>>{std::list<CharT>{input[0].begin(), input[0].end()}},
+                 test_range_format_string<std::list<CharT>>{std::list<CharT>{input[1].begin(), input[1].end()}}});
+  test_range_string<CharT>(
+      check,
+      check_exception,
+      std::list{test_range_format_string<std::list<CharT>>{std::list<CharT>{input[0].begin(), input[0].end()}},
+                test_range_format_string<std::list<CharT>>{std::list<CharT>{input[1].begin(), input[1].end()}}});
+}
+
+//
+// Debug string
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_debug_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  check(SV("\"hello\""), SV("{}"), input);
+
+  // *** align-fill & width ***
+  check(SV("\"hello\"     "), SV("{:12}"), input);
+  check(SV("\"hello\"*****"), SV("{:*<12}"), input);
+  check(SV("__\"hello\"___"), SV("{:_^12}"), input);
+  check(SV(":::::\"hello\""), SV("{::>12}"), input);
+
+  check(SV("\"hello\"     "), SV("{:{}}"), input, 12);
+  check(SV("\"hello\"*****"), SV("{:*<{}}"), input, 12);
+  check(SV("__\"hello\"___"), SV("{:_^{}}"), input, 12);
+  check(SV(":::::\"hello\""), SV("{::>{}}"), input, 12);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{:{<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check(SV("\"he"), SV("{:.3}"), input);
+  check(SV("\"he"), SV("{:.{}}"), input, 3);
+
+  check(SV("\"he  "), SV("{:5.3}"), input);
+  check(SV("\"he  "), SV("{:{}.{}}"), input, 5, 3);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** type ***
+  check(SV("\"hello\""), SV("{:s}"), input); // escape overrides the type option s
+  check(SV("\"hello\""), SV("{:?}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s?"))
+    check_exception("The format-spec type has a type not supported for a string argument", fmt, input);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_debug_string(TestFunction check, ExceptionTest check_exception) {
+  // libc++ uses 
diff erent containers for contiguous and non-contiguous ranges.
+  std::basic_string<CharT> input = STR("hello");
+  test_debug_string<CharT>(check, check_exception, test_range_format_debug_string<std::basic_string<CharT>>{input});
+  test_debug_string<CharT>(
+      check, check_exception, test_range_format_debug_string<std::basic_string_view<CharT>>{input});
+  test_debug_string<CharT>(
+      check,
+      check_exception,
+      test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input.begin(), input.end()}});
+}
+
+//
+// Debug string range
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_range_debug_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV(R"(["Hello", "world"]     )"), SV("{:23}"), input);
+  check(SV(R"(["Hello", "world"]*****)"), SV("{:*<23}"), input);
+  check(SV(R"(__["Hello", "world"]___)"), SV("{:_^23}"), input);
+  check(SV(R"(#####["Hello", "world"])"), SV("{:#>23}"), input);
+
+  check(SV(R"(["Hello", "world"]     )"), SV("{:{}}"), input, 23);
+  check(SV(R"(["Hello", "world"]*****)"), SV("{:*<{}}"), input, 23);
+  check(SV(R"(__["Hello", "world"]___)"), SV("{:_^{}}"), input, 23);
+  check(SV(R"(#####["Hello", "world"])"), SV("{:#>{}}"), input, 23);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV(R"(_"Hello", "world"_)"), SV("{:_^18n}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV(R"(["Hello"   , "world"   ])"), SV("{::10}"), input);
+  check(SV(R"(["Hello"***, "world"***])"), SV("{::*<10}"), input);
+  check(SV(R"([_"Hello"__, _"world"__])"), SV("{::_^10}"), input);
+  check(SV(R"([:::"Hello", :::"world"])"), SV("{:::>10}"), input);
+
+  check(SV(R"(["Hello"   , "world"   ])"), SV("{::{}}"), input, 10);
+  check(SV(R"(["Hello"***, "world"***])"), SV("{::*<{}}"), input, 10);
+  check(SV(R"([_"Hello"__, _"world"__])"), SV("{::_^{}}"), input, 10);
+  check(SV(R"([:::"Hello", :::"world"])"), SV("{:::>{}}"), input, 10);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::-}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{::05}"), input);
+
+  // *** precision ***
+  check(SV(R"(["He, "wo])"), SV("{::.3}"), input);
+
+  check(SV(R"(["He, "wo])"), SV("{::.{}}"), input, 3);
+
+  check_exception("The format-spec precision field doesn't contain a value or arg-id", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::L}"), input);
+
+  // *** type ***
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("s?"))
+    check_exception("The format-spec type has a type not supported for a string argument", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^29::>10}"), input);
+  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>10}"), input, 29);
+  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>{}}"), input, 29, 10);
+
+  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^29::>10}"), input);
+  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>10}"), input, 29);
+  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>{}}"), input, 29, 10);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}::>10}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 29);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_range_debug_string(TestFunction check, ExceptionTest check_exception) {
+  // libc++ uses 
diff erent containers for contiguous and non-contiguous ranges.
+  std::array input{STR("Hello"), STR("world")};
+  test_range_debug_string<CharT>(
+      check,
+      check_exception,
+      std::array{test_range_format_debug_string<std::basic_string<CharT>>{input[0]},
+                 test_range_format_debug_string<std::basic_string<CharT>>{input[1]}});
+  test_range_debug_string<CharT>(
+      check,
+      check_exception,
+      std::array{test_range_format_debug_string<std::basic_string_view<CharT>>{input[0]},
+                 test_range_format_debug_string<std::basic_string_view<CharT>>{input[1]}});
+  test_range_debug_string<CharT>(
+      check,
+      check_exception,
+      std::array{test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input[0].begin(), input[0].end()}},
+                 test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input[1].begin(), input[1].end()}}});
+  test_range_debug_string<CharT>(
+      check,
+      check_exception,
+      std::list{test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input[0].begin(), input[0].end()}},
+                test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input[1].begin(), input[1].end()}}});
+}
+
+//
+// Driver
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void format_tests(TestFunction check, ExceptionTest check_exception) {
+  test_string<CharT>(check, check_exception);
+  test_range_string<CharT>(check, check_exception);
+
+  test_debug_string<CharT>(check, check_exception);
+  test_range_debug_string<CharT>(check, check_exception);
+}
+
+#endif //  TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FMTSTR_FORMAT_FUNCTIONS_TESTS_H

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.vformat.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.vformat.pass.cpp
new file mode 100644
index 0000000000000..a549b692091b3
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.functions.vformat.pass.cpp
@@ -0,0 +1,65 @@
+//===----------------------------------------------------------------------===//
+// 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, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// XFAIL: availability-fp_to_chars-missing
+
+// <format>
+
+// template<ranges::input_range R, class charT>
+//   requires (K == range_format::string || K == range_format::debug_string)
+// struct range-default-formatter<K, R, charT>
+//
+// string vformat(string_view fmt, format_args args);
+// wstring vformat(wstring_view fmt, wformat_args args);
+
+#include <format>
+#include <cassert>
+
+#include "format.functions.tests.h"
+#include "test_macros.h"
+#include "assert_macros.h"
+#include "concat_macros.h"
+
+auto test = []<class CharT, class... Args>(
+                std::basic_string_view<CharT> expected, std::basic_string_view<CharT> fmt, Args&&... args) {
+  std::basic_string<CharT> out = std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...));
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception =
+    []<class CharT, class... Args>(
+        [[maybe_unused]] std::string_view what,
+        [[maybe_unused]] std::basic_string_view<CharT> fmt,
+        [[maybe_unused]] Args&&... args) {
+      TEST_VALIDATE_EXCEPTION(
+          std::format_error,
+          [&]([[maybe_unused]] const std::format_error& e) {
+            TEST_LIBCPP_REQUIRE(
+                e.what() == what,
+                TEST_WRITE_CONCATENATED(
+                    "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+          },
+          TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...)));
+    };
+
+int main(int, char**) {
+  format_tests<char>(test, test_exception);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  format_tests<wchar_t>(test, test_exception);
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.pass.cpp
new file mode 100644
index 0000000000000..9ae44b3e4e63d
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/format.pass.cpp
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+// 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, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Investigate why this fails.
+// UNSUPPORTED: stdlib=apple-libc++ && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13|10.14|10.15|11.0|12.0}}
+
+// <format>
+
+// template<ranges::input_range R, class charT>
+//   requires (K == range_format::string || K == range_format::debug_string)
+// struct range-default-formatter<K, R, charT>
+
+// template<class FormatContext>
+//   typename FormatContext::iterator
+//     format(const T& ref, FormatContext& ctx) const;
+
+// Note this tests the basics of this function. It's tested in more detail in
+// the format.functions test.
+
+#include <cassert>
+#include <concepts>
+#include <format>
+
+#include "format.functions.tests.h"
+#include "test_format_context.h"
+#include "test_macros.h"
+
+template <class StringViewT, class ArgT>
+void test_format(StringViewT expected, ArgT arg) {
+  using CharT      = typename StringViewT::value_type;
+  using String     = std::basic_string<CharT>;
+  using OutIt      = std::back_insert_iterator<String>;
+  using FormatCtxT = std::basic_format_context<OutIt, CharT>;
+
+  std::formatter<ArgT, CharT> formatter;
+
+  String result;
+  OutIt out             = std::back_inserter(result);
+  FormatCtxT format_ctx = test_format_context_create<OutIt, CharT>(out, std::make_format_args<FormatCtxT>(arg));
+  formatter.format(arg, format_ctx);
+  assert(result == expected);
+}
+
+template <class CharT>
+void test_fmt() {
+  test_format(SV("hello"), test_range_format_string<std::basic_string<CharT>>{STR("hello")});
+  test_format(SV("hello"), test_range_format_debug_string<std::basic_string<CharT>>{STR("hello")});
+}
+
+void test() {
+  test_fmt<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_fmt<wchar_t>();
+#endif
+}
+
+int main(int, char**) {
+  test();
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/parse.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/parse.pass.cpp
new file mode 100644
index 0000000000000..5aaeae51e1d76
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.fmtstr/parse.pass.cpp
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+// 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, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Investigate why this fails.
+// UNSUPPORTED: stdlib=apple-libc++ && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13|10.14|10.15|11.0|12.0}}
+
+// <format>
+
+// template<ranges::input_range R, class charT>
+//   requires (K == range_format::string || K == range_format::debug_string)
+// struct range-default-formatter<K, R, charT>
+
+// template<class ParseContext>
+//   constexpr typename ParseContext::iterator
+//     parse(ParseContext& ctx);
+
+// Note this tests the basics of this function. It's tested in more detail in
+// the format.functions test.
+
+#include <cassert>
+#include <concepts>
+#include <format>
+
+#include "format.functions.tests.h"
+#include "test_format_context.h"
+#include "test_macros.h"
+
+template <class FormatterT, class StringViewT>
+constexpr void test_parse(StringViewT fmt) {
+  using CharT    = typename StringViewT::value_type;
+  auto parse_ctx = std::basic_format_parse_context<CharT>(fmt);
+  FormatterT formatter;
+  static_assert(std::semiregular<decltype(formatter)>);
+
+  std::same_as<typename StringViewT::iterator> auto it = formatter.parse(parse_ctx);
+  assert(it == fmt.end() - (!fmt.empty() && fmt.back() == '}'));
+}
+
+template <class StringViewT>
+constexpr void test_formatters(StringViewT fmt) {
+  using CharT = typename StringViewT::value_type;
+  test_parse<std::formatter<test_range_format_string<std::basic_string<CharT>>, CharT>>(fmt);
+  test_parse<std::formatter<test_range_format_debug_string<std::basic_string<CharT>>, CharT>>(fmt);
+}
+
+template <class CharT>
+constexpr void test_char_type() {
+  test_formatters(SV(""));
+  test_formatters(SV("}"));
+}
+
+constexpr bool test() {
+  test_char_type<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_char_type<wchar_t>();
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}


        


More information about the libcxx-commits mailing list