[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