Skip to content

Commit

Permalink
fmtlib#2390: consteval format checks for wchar_t and char8_t
Browse files Browse the repository at this point in the history
  • Loading branch information
alabuzhev committed Jun 26, 2021
1 parent e5c46e1 commit 8cb5518
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 8 deletions.
27 changes: 26 additions & 1 deletion include/fmt/xchar.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ using wmemory_buffer = basic_memory_buffer<wchar_t>;

#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
template <typename Char, typename... Args> using basic_string_view<Char>;
template <typename... Args> using wformat_string = wstring_view;
#else
template <typename Char, typename... Args>
using xformat_string = basic_format_string<Char, type_identity_t<Args>...>;
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
#endif
Expand All @@ -40,6 +43,12 @@ template <> struct is_char<detail::char8_type> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};

template <typename Char, typename... Args>
constexpr format_arg_store<buffer_context<Char>, Args...> make_xformat_args(
const Args&... args) {
return {args...};
}

template <typename... Args>
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
const Args&... args) {
Expand Down Expand Up @@ -87,10 +96,26 @@ auto vformat(basic_string_view<Char> format_str,
return to_string(buffer);
}

template <typename... T>
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
return vformat<wchar_t>(fmt, make_xformat_args<wchar_t>(args...));
}

template <typename... T>
auto format(xformat_string<detail::char8_type, T...> fmt, T&&... args)
-> std::basic_string<detail::char8_type> {
return vformat<detail::char8_type>(
fmt, make_xformat_args<detail::char8_type>(args...));
}

// Pass char_t as a default template parameter instead of using
// std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... Args, typename Char = char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
(!std::is_convertible<
S, xformat_string<char_t<S>, Args...>>::value ||
(!std::is_same<Char, wchar_t>::value &&
!std::is_same<Char, detail::char8_type>::value)))>
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat(to_string_view(format_str), vargs);
Expand Down
18 changes: 12 additions & 6 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#endif
// clang-format off
#include "fmt/format.h"
#include "fmt/xchar.h"
// clang-format on

#include <stdint.h> // uint32_t
Expand All @@ -33,6 +34,7 @@ using fmt::memory_buffer;
using fmt::runtime;
using fmt::string_view;
using fmt::detail::max_value;
using fmt::char_t;

using testing::Return;
using testing::StrictMock;
Expand Down Expand Up @@ -1949,7 +1951,8 @@ struct test_error_handler {
}
};

FMT_CONSTEXPR size_t len(const char* s) {
template<typename Char>
FMT_CONSTEXPR size_t len(const Char* s) {
size_t len = 0;
while (*s++) ++len;
return len;
Expand All @@ -1964,12 +1967,12 @@ FMT_CONSTEXPR bool equal(const char* s1, const char* s2) {
return *s1 == *s2;
}

template <typename... Args>
FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) {
template <typename Char, typename... Args>
FMT_CONSTEXPR bool test_error(const Char* fmt, const char* expected_error) {
const char* actual_error = nullptr;
auto s = string_view(fmt, len(fmt));
auto s = fmt::basic_string_view<Char>(fmt, len(fmt));
auto checker =
fmt::detail::format_string_checker<char, test_error_handler, Args...>(
fmt::detail::format_string_checker<Char, test_error_handler, Args...>(
s, test_error_handler(actual_error));
fmt::detail::parse_format_string<true>(s, checker);
return equal(actual_error, expected_error);
Expand All @@ -1978,7 +1981,7 @@ FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) {
# define EXPECT_ERROR_NOARGS(fmt, error) \
static_assert(test_error(fmt, error), "")
# define EXPECT_ERROR(fmt, error, ...) \
static_assert(test_error<__VA_ARGS__>(fmt, error), "")
static_assert(test_error<char_t<decltype(fmt)>, __VA_ARGS__>(fmt, error), "")

TEST(format_test, format_string_errors) {
EXPECT_ERROR_NOARGS("foo", nullptr);
Expand Down Expand Up @@ -2041,6 +2044,9 @@ TEST(format_test, format_string_errors) {
EXPECT_ERROR("{}{1}",
"cannot switch from automatic to manual argument indexing", int,
int);

EXPECT_ERROR(L"{:d}", "invalid type specifier", decltype(L""));
EXPECT_ERROR(u8"{:d}", "invalid type specifier", decltype(u8""));
}

TEST(format_test, vformat_to) {
Expand Down
2 changes: 1 addition & 1 deletion test/xchar-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ TEST(xchar_test, sign_not_truncated) {
wchar_t format_str[] = {
L'{', L':',
'+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
EXPECT_THROW(fmt::format(format_str, 42), fmt::format_error);
EXPECT_THROW(fmt::format(fmt::runtime(format_str), 42), fmt::format_error);
}

namespace fake_qt {
Expand Down

0 comments on commit 8cb5518

Please sign in to comment.