From 92d9f232dd627eea8bd2c825c539b28374bbfa69 Mon Sep 17 00:00:00 2001 From: Mark de Wever Date: Fri, 24 Nov 2023 17:30:33 +0100 Subject: [PATCH] [libc++] Implements Runtime format strings II. (#72543) 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 f223399cd3f0..9dfdc5905115 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 eb5398f66d0e..e7ddc906b622 100644 --- a/libcxx/docs/Status/Cxx2cPapers.csv +++ b/libcxx/docs/Status/Cxx2cPapers.csv @@ -31,7 +31,7 @@ "`P2407R5 `__","LWG","Freestanding Library: Partial Classes","Kona November 2023","","","" "`P2546R5 `__","LWG","Debugging Support","Kona November 2023","","","" "`P2905R2 `__","LWG","Runtime format strings","Kona November 2023","","","|format| |DR|" -"`P2918R2 `__","LWG","Runtime format strings II","Kona November 2023","","","|format|" +"`P2918R2 `__","LWG","Runtime format strings II","Kona November 2023","|Complete|","18.0","|format|" "`P2909R4 `__","LWG","Fix formatting of code units as integers (Dude, where’s my ``char``?)","Kona November 2023","","","|format| |DR|" "`P0952R2 `__","LWG","A new specification for ``std::generate_canonical``","Kona November 2023","","","" "`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 5c6463fa97ed..14ea7c4360a3 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 `__","Type-checking format args","C++26","","", "`P2637R3 `__","Member ``visit``","C++26","","", "`P2905R2 `__","Runtime format strings","C++26 DR","Mark de Wever","|In Progress|" -"`P2918R2 `__","Runtime format strings II","C++26","Mark de Wever","|In Progress|" +"`P2918R2 `__","Runtime format strings II","C++26","Mark de Wever","|Complete|",18.0 "`P2909R4 `__","Fix formatting of code units as integers (Dude, where’s my ``char``?)","C++26 DR","Mark de Wever","|In Progress|" `P1361 `_,"Integration of chrono with text formatting","C++20",Mark de Wever,|In Progress|, `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 bb62c1ce10c1..d4d36d68a98c 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 +struct _LIBCPP_TEMPLATE_VIS __runtime_format_string { +private: + basic_string_view<_CharT> __str_; + + template + 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 runtime_format(string_view __fmt) noexcept { return __fmt; } +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +_LIBCPP_HIDE_FROM_ABI inline __runtime_format_string runtime_format(wstring_view __fmt) noexcept { + return __fmt; +} +# endif +# endif //_LIBCPP_STD_VER >= 26 + template struct _LIBCPP_TEMPLATE_VIS basic_format_string { template @@ -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 c48bcf6e8403..7b8d5922cb49 100644 --- a/libcxx/include/format +++ b/libcxx/include/format @@ -31,6 +31,7 @@ namespace std { public: template consteval basic_format_string(const T& s); + basic_format_string(runtime-format-string s) noexcept : str(s.str) {} // since C++26 constexpr basic_string_view 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...>; + template struct runtime-format-string { // since C++26, exposition-only + private: + basic_string_view str; // exposition-only + + public: + runtime-format-string(basic_string_view s) noexcept : str(s) {} + + runtime-format-string(const runtime-format-string&) = delete; + runtime-format-string& operator=(const runtime-format-string&) = delete; + }; + + runtime-format-string runtime_format(string_view fmt) noexcept { + return fmt; + } + runtime-format-string runtime_format(wstring_view fmt) noexcept { + return fmt; + } + // [format.functions], formatting functions template string format(format-string fmt, Args&&... args); diff --git a/libcxx/modules/std/format.inc b/libcxx/modules/std/format.inc index c1bc91f8317d..743a43811005 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 000000000000..fff15a1da401 --- /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 + +// + +// template +// class basic_format_string...> +// +// basic_format_string(runtime-format-string 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 +#include + +#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 000000000000..0ddb597c2d09 --- /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 + +// + +// Tests the behavior of +// +// runtime-format-string runtime_format(string_view fmt) noexcept; +// runtime-format-string runtime_format(wstring_view fmt) noexcept; +// +// and +// +// template +// struct basic_format_string { +// ... +// basic_format_string(runtime-format-string s) noexcept : str(s.str) {} +// ... +// } +// +// This is done by testing it in the top-level functions: +// +// template +// string format(const locale& loc, format_string fmt, Args&&... args); +// template +// wstring format(const locale& loc, wformat_string 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 +#include +#include +#include + +#include "test_macros.h" +#include "format_tests.h" +#include "string_literal.h" +#include "assert_macros.h" +#include "concat_macros.h" + +auto test = []( + std::basic_string_view expected, std::basic_string_view fmt, Args&&... args) constexpr { + std::basic_string out = std::format(std::locale(), std::runtime_format(fmt), std::forward(args)...); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = + []( + [[maybe_unused]] std::string_view what, + [[maybe_unused]] std::basic_string_view 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)...)); + }; + +int main(int, char**) { + format_tests(test, test_exception); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + format_tests_char_to_wchar_t(test); + format_tests(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 000000000000..089dd615451b --- /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 + +// + +// Tests the behavior of +// +// runtime-format-string runtime_format(string_view fmt) noexcept; +// runtime-format-string runtime_format(wstring_view fmt) noexcept; +// +// and +// +// template +// struct basic_format_string { +// ... +// basic_format_string(runtime-format-string s) noexcept : str(s.str) {} +// ... +// } +// +// This is done by testing it in the top-level functions: +// +// template +// string format(format_string fmt, Args&&... args); +// template +// wstring format(wformat_string 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 +#include +#include + +#include "test_macros.h" +#include "format_tests.h" +#include "string_literal.h" +#include "assert_macros.h" +#include "concat_macros.h" + +auto test = []( + std::basic_string_view expected, std::basic_string_view fmt, Args&&... args) constexpr { + std::basic_string out = std::format(std::runtime_format(fmt), std::forward(args)...); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = + []( + [[maybe_unused]] std::string_view what, + [[maybe_unused]] std::basic_string_view 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)...)); + }; + +int main(int, char**) { + format_tests(test, test_exception); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + format_tests_char_to_wchar_t(test); + format_tests(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 000000000000..c2a221c233ba --- /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 + +// + +// template struct runtime-format-string { // exposition-only +// private: +// basic_string_view str; // exposition-only +// +// public: +// runtime-format-string(basic_string_view s) noexcept : str(s) {} +// +// runtime-format-string(const runtime-format-string&) = delete; +// runtime-format-string& operator=(const runtime-format-string&) = delete; +// }; +// +// runtime-format-string runtime_format(string_view fmt) noexcept; +// runtime-format-string 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 + +#include +#include +#include +#include + +#include "test_macros.h" + +template +static void test_properties() { + static_assert(std::is_nothrow_convertible_v, T>); + static_assert(std::is_nothrow_constructible_v>); + + static_assert(!std::copy_constructible); + static_assert(!std::is_copy_assignable_v); + + static_assert(!std::move_constructible); + static_assert(!std::is_move_assignable_v); +} + +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>)); + test_properties(); + +#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>)); + test_properties(); +#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 589845a3c13d..328b138b6a10 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -467,7 +467,7 @@ feature_test_macros = [ # "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)