[libcxx-commits] [libcxx] [libc++] Implements Runtime format strings II. (PR #72543)

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Thu Nov 23 11:45:06 PST 2023


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

>From f73eeacb3e2c91a171cc34661f11b2b23b2c1ed9 Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Tue, 14 Nov 2023 18:08:25 +0100
Subject: [PATCH] [libc++] Implements Runtime format strings II.

Implements
- P2918R2 Runtime format strings II
---
 libcxx/docs/ReleaseNotes/18.rst               |  1 +
 libcxx/docs/Status/Cxx2cPapers.csv            |  2 +-
 libcxx/docs/Status/FormatIssues.csv           |  2 +-
 libcxx/include/__format/format_functions.h    | 27 ++++++
 libcxx/include/format                         | 19 +++++
 libcxx/modules/std/format.inc                 |  3 +
 .../ctor.runtime-format-string.pass.cpp       | 43 ++++++++++
 .../format.locale.runtime_format.pass.cpp     | 85 +++++++++++++++++++
 .../format.runtime_format.pass.cpp            | 83 ++++++++++++++++++
 .../format.syn/runtime_format_string.pass.cpp | 70 +++++++++++++++
 .../generate_feature_test_macro_components.py |  2 +-
 11 files changed, 334 insertions(+), 3 deletions(-)
 create mode 100644 libcxx/test/std/utilities/format/format.fmt.string/ctor.runtime-format-string.pass.cpp
 create mode 100644 libcxx/test/std/utilities/format/format.functions/format.locale.runtime_format.pass.cpp
 create mode 100644 libcxx/test/std/utilities/format/format.functions/format.runtime_format.pass.cpp
 create mode 100644 libcxx/test/std/utilities/format/format.syn/runtime_format_string.pass.cpp

diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index f223399cd3f0f92..9dfdc590511592b 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -50,6 +50,7 @@ Implemented Papers
 - P0053R7 - C++ Synchronized Buffered Ostream (in the experimental library)
 - P2467R1 - Support exclusive mode for fstreams
 - P0020R6 - Floating Point Atomic
+- P2918R2 - Runtime format strings II
 
 
 Improvements and New Features
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index eb5398f66d0e0c1..e7ddc906b622bd9 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -31,7 +31,7 @@
 "`P2407R5 <https://wg21.link/P2407R5>`__","LWG","Freestanding Library: Partial Classes","Kona November 2023","","",""
 "`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","","","|format|"
+"`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|"
 "`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","","",""
diff --git a/libcxx/docs/Status/FormatIssues.csv b/libcxx/docs/Status/FormatIssues.csv
index 5c6463fa97ed2ba..14ea7c4360a373f 100644
--- a/libcxx/docs/Status/FormatIssues.csv
+++ b/libcxx/docs/Status/FormatIssues.csv
@@ -18,7 +18,7 @@ Number,Name,Standard,Assignee,Status,First released version
 "`P2757R3 <https://wg21.link/P2757R3>`__","Type-checking format args","C++26","","",
 "`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","|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|"
 `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|,
diff --git a/libcxx/include/__format/format_functions.h b/libcxx/include/__format/format_functions.h
index bb62c1ce10c15cb..d4d36d68a98cc23 100644
--- a/libcxx/include/__format/format_functions.h
+++ b/libcxx/include/__format/format_functions.h
@@ -338,6 +338,30 @@ __vformat_to(_ParseCtx&& __parse_ctx, _Ctx&& __ctx) {
 
 } // namespace __format
 
+#  if _LIBCPP_STD_VER >= 26
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS __runtime_format_string {
+private:
+  basic_string_view<_CharT> __str_;
+
+  template <class _Cp, class... _Args>
+  friend struct _LIBCPP_TEMPLATE_VIS basic_format_string;
+
+public:
+  _LIBCPP_HIDE_FROM_ABI __runtime_format_string(basic_string_view<_CharT> __s) noexcept : __str_(__s) {}
+
+  __runtime_format_string(const __runtime_format_string&)            = delete;
+  __runtime_format_string& operator=(const __runtime_format_string&) = delete;
+};
+
+_LIBCPP_HIDE_FROM_ABI inline __runtime_format_string<char> runtime_format(string_view __fmt) noexcept { return __fmt; }
+#    ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+_LIBCPP_HIDE_FROM_ABI inline __runtime_format_string<wchar_t> runtime_format(wstring_view __fmt) noexcept {
+  return __fmt;
+}
+#    endif
+#  endif //_LIBCPP_STD_VER >= 26
+
 template <class _CharT, class... _Args>
 struct _LIBCPP_TEMPLATE_VIS basic_format_string {
   template <class _Tp>
@@ -350,6 +374,9 @@ struct _LIBCPP_TEMPLATE_VIS basic_format_string {
   _LIBCPP_HIDE_FROM_ABI constexpr basic_string_view<_CharT> get() const noexcept {
     return __str_;
   }
+#  if _LIBCPP_STD_VER >= 26
+  _LIBCPP_HIDE_FROM_ABI basic_format_string(__runtime_format_string<_CharT> __s) noexcept : __str_(__s.__str_) {}
+#  endif
 
 private:
   basic_string_view<_CharT> __str_;
diff --git a/libcxx/include/format b/libcxx/include/format
index c48bcf6e8403930..8a4837a868a9845 100644
--- a/libcxx/include/format
+++ b/libcxx/include/format
@@ -31,6 +31,7 @@ namespace std {
 
     public:
       template<class T> consteval basic_format_string(const T& s);
+	  basic_format_string(runtime-format-string<charT> s) noexcept : str(s.str) {}   // since C++26
 
       constexpr basic_string_view<charT> get() const noexcept { return str; }
     };
@@ -41,6 +42,24 @@ namespace std {
     using wformat_string =                                      // since C++23, exposition only before C++23
       basic_format_string<wchar_t, type_identity_t<Args>...>;
 
+  template<class charT> struct runtime-format-string {          // since C++26, exposition-only
+  private:
+    basic_string_view<charT> str;                               // exposition-only
+
+  public:
+    runtime-format-string(basic_string_view<charT> s) noexcept : str(s) {}
+
+    runtime-format-string(const runtime-format-string&) = delete;
+    runtime-format-string& operator=(const runtime-format-string&) = delete;
+  };
+
+  runtime-format-string<char> runtime_format(string_view fmt) noexcept {
+    return fmt;
+  }
+  runtime-format-string<wchar_t> runtime_format(wstring_view fmt) noexcept {
+    return fmt;
+  }
+
   // [format.functions], formatting functions
   template<class... Args>
     string format(format-string<Args...> fmt, Args&&... args);
diff --git a/libcxx/modules/std/format.inc b/libcxx/modules/std/format.inc
index c1bc91f8317dd0a..743a43811005a4b 100644
--- a/libcxx/modules/std/format.inc
+++ b/libcxx/modules/std/format.inc
@@ -28,6 +28,9 @@ export namespace std {
 #ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
   using std::wformat_string;
 #endif
+#if _LIBCPP_STD_VER >= 26
+  using std::runtime_format;
+#endif //_LIBCPP_STD_VER >= 26
 
   // [format.functions], formatting functions
   using std::format;
diff --git a/libcxx/test/std/utilities/format/format.fmt.string/ctor.runtime-format-string.pass.cpp b/libcxx/test/std/utilities/format/format.fmt.string/ctor.runtime-format-string.pass.cpp
new file mode 100644
index 000000000000000..fff15a1da40178c
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.fmt.string/ctor.runtime-format-string.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++23
+
+// <format>
+
+// template<class charT, class... Args>
+// class basic_format_string<charT, type_identity_t<Args>...>
+//
+// basic_format_string(runtime-format-string<charT> s) noexcept : str(s.str) {}
+//
+// Additional testing is done in
+// - libcxx/test/std/utilities/format/format.functions/format.runtime_format.pass.cpp
+// - libcxx/test/std/utilities/format/format.functions/format.locale.runtime_format.pass.cpp
+
+#include <format>
+#include <cassert>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+  static_assert(noexcept(std::format_string<>{std::runtime_format(std::string_view{})}));
+  {
+    std::format_string<> s = std::runtime_format("}{invalid format string}{");
+    assert(s.get() == "}{invalid format string}{");
+  }
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  static_assert(noexcept(std::wformat_string<>{std::runtime_format(std::wstring_view{})}));
+  {
+    std::wformat_string<> s = std::runtime_format(L"}{invalid format string}{");
+    assert(s.get() == L"}{invalid format string}{");
+  }
+#endif // TEST_HAS_NO_WIDE_CHARACTERS
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.functions/format.locale.runtime_format.pass.cpp b/libcxx/test/std/utilities/format/format.functions/format.locale.runtime_format.pass.cpp
new file mode 100644
index 000000000000000..0ddb597c2d09287
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.functions/format.locale.runtime_format.pass.cpp
@@ -0,0 +1,85 @@
+//===----------------------------------------------------------------------===//
+// 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, c++23
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
+
+// XFAIL: availability-fp_to_chars-missing
+
+// <format>
+
+// Tests the behavior of
+//
+// runtime-format-string<char> runtime_format(string_view fmt) noexcept;
+// runtime-format-string<wchar_t> runtime_format(wstring_view fmt) noexcept;
+//
+// and
+//
+// template<class charT, class... Args>
+//   struct basic_format_string {
+//   ...
+//   basic_format_string(runtime-format-string<charT> s) noexcept : str(s.str) {}
+//   ...
+// }
+//
+// This is done by testing it in the top-level functions:
+//
+// template<class... Args>
+//   string format(const locale& loc, format_string<Args...> fmt, Args&&... args);
+// template<class... Args>
+//   wstring format(const locale& loc, wformat_string<Args...> fmt, Args&&... args);
+//
+// The basics of runtime_format and basic_format_string's constructor are tested in
+// - libcxx/test/std/utilities/format/format.syn/runtime_format_string.pass.cpp
+// - libcxx/test/std/utilities/format/format.fmt.string/ctor.runtime-format-string.pass.cpp
+
+#include <format>
+#include <cassert>
+#include <locale>
+#include <vector>
+
+#include "test_macros.h"
+#include "format_tests.h"
+#include "string_literal.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) constexpr {
+  std::basic_string<CharT> out = std::format(std::locale(), std::runtime_format(fmt), std::forward<Args>(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::format(std::locale(), std::runtime_format(fmt), std::forward<Args>(args)...));
+    };
+
+int main(int, char**) {
+  format_tests<char, execution_modus::partial>(test, test_exception);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  format_tests_char_to_wchar_t(test);
+  format_tests<wchar_t, execution_modus::partial>(test, test_exception);
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.functions/format.runtime_format.pass.cpp b/libcxx/test/std/utilities/format/format.functions/format.runtime_format.pass.cpp
new file mode 100644
index 000000000000000..089dd615451b381
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.functions/format.runtime_format.pass.cpp
@@ -0,0 +1,83 @@
+//===----------------------------------------------------------------------===//
+// 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, c++23
+// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
+
+// XFAIL: availability-fp_to_chars-missing
+
+// <format>
+
+// Tests the behavior of
+//
+// runtime-format-string<char> runtime_format(string_view fmt) noexcept;
+// runtime-format-string<wchar_t> runtime_format(wstring_view fmt) noexcept;
+//
+// and
+//
+// template<class charT, class... Args>
+//   struct basic_format_string {
+//   ...
+//   basic_format_string(runtime-format-string<charT> s) noexcept : str(s.str) {}
+//   ...
+// }
+//
+// This is done by testing it in the top-level functions:
+//
+// template<class... Args>
+//   string format(format_string<Args...> fmt, Args&&... args);
+// template<class... Args>
+//   wstring format(wformat_string<Args...> fmt, Args&&... args);
+//
+// The basics of runtime_format and basic_format_string's constructor are tested in
+// - libcxx/test/std/utilities/format/format.syn/runtime_format_string.pass.cpp
+// - libcxx/test/std/utilities/format/format.fmt.string/ctor.runtime-format-string.pass.cpp
+
+#include <format>
+#include <cassert>
+#include <vector>
+
+#include "test_macros.h"
+#include "format_tests.h"
+#include "string_literal.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) constexpr {
+  std::basic_string<CharT> out = std::format(std::runtime_format(fmt), std::forward<Args>(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::format(std::runtime_format(fmt), std::forward<Args>(args)...));
+    };
+
+int main(int, char**) {
+  format_tests<char, execution_modus::partial>(test, test_exception);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  format_tests_char_to_wchar_t(test);
+  format_tests<wchar_t, execution_modus::partial>(test, test_exception);
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.syn/runtime_format_string.pass.cpp b/libcxx/test/std/utilities/format/format.syn/runtime_format_string.pass.cpp
new file mode 100644
index 000000000000000..c2a221c233ba4fb
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.syn/runtime_format_string.pass.cpp
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++23
+
+// <format>
+
+// template<class charT> struct runtime-format-string {  // exposition-only
+// private:
+//   basic_string_view<charT> str;  // exposition-only
+//
+// public:
+//   runtime-format-string(basic_string_view<charT> s) noexcept : str(s) {}
+//
+//   runtime-format-string(const runtime-format-string&) = delete;
+//   runtime-format-string& operator=(const runtime-format-string&) = delete;
+// };
+//
+// runtime-format-string<char> runtime_format(string_view fmt) noexcept;
+// runtime-format-string<wchar_t> runtime_format(wstring_view fmt) noexcept;
+//
+// Additional testing is done in
+// - libcxx/test/std/utilities/format/format.functions/format.runtime_format.pass.cpp
+// - libcxx/test/std/utilities/format/format.functions/format.locale.runtime_format.pass.cpp
+
+#include <format>
+
+#include <cassert>
+#include <concepts>
+#include <string_view>
+#include <type_traits>
+
+#include "test_macros.h"
+
+template <class T, class CharT>
+static void test_properties() {
+  static_assert(std::is_nothrow_convertible_v<std::basic_string_view<CharT>, T>);
+  static_assert(std::is_nothrow_constructible_v<T, std::basic_string_view<CharT>>);
+
+  static_assert(!std::copy_constructible<T>);
+  static_assert(!std::is_copy_assignable_v<T>);
+
+  static_assert(!std::move_constructible<T>);
+  static_assert(!std::is_move_assignable_v<T>);
+}
+
+int main(int, char**) {
+  static_assert(noexcept(std::runtime_format(std::string_view{})));
+  auto format_string = std::runtime_format(std::string_view{});
+
+  using FormatString = decltype(format_string);
+  LIBCPP_ASSERT((std::same_as<FormatString, std::__runtime_format_string<char>>));
+  test_properties<FormatString, char>();
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  static_assert(noexcept(std::runtime_format(std::wstring_view{})));
+  auto wformat_string = std::runtime_format(std::wstring_view{});
+
+  using WFormatString = decltype(wformat_string);
+  LIBCPP_ASSERT((std::same_as<WFormatString, std::__runtime_format_string<wchar_t>>));
+  test_properties<WFormatString, wchar_t>();
+#endif // TEST_HAS_NO_WIDE_CHARACTERS
+
+  return 0;
+}
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 589845a3c13d781..328b138b6a10a60 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -467,7 +467,7 @@ def add_version_header(tc):
                 # "c++20": 202110 Not implemented P2372R3 Fixing locale handling in chrono formatters
                 "c++20": 202106,
                 # "c++23": 202207, Not implemented P2419R2 Clarify handling of encodings in localized formatting of chrono types
-                # "c++26": 202311, Not implemented P2918R2 Runtime format strings II
+                # "c++26": 202311, P2918R2 Runtime format strings II (implemented)
             },
             # Note these three papers are adopted at the June 2023 meeting and have sequential numbering
             # 202304 P2510R3 Formatting pointers (Implemented)



More information about the libcxx-commits mailing list