Skip to content

[libc++] Implements Runtime format strings II. #72543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libcxx/docs/ReleaseNotes/18.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2cPapers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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","","",""
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/FormatIssues.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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|,
Expand Down
27 changes: 27 additions & 0 deletions libcxx/include/__format/format_functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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_;
Expand Down
19 changes: 19 additions & 0 deletions libcxx/include/format
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
};
Expand All @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions libcxx/modules/std/format.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Loading