Improve compile-time checks

This commit is contained in:
Victor Zverovich 2021-05-18 19:38:52 -07:00
parent 21d93bfd33
commit e9c1c415b8
8 changed files with 343 additions and 298 deletions

View File

@ -1642,27 +1642,6 @@ constexpr format_arg_store<Context, Args...> make_format_args(
return {args...}; return {args...};
} }
/**
\rst
Constructs a `~fmt::format_arg_store` object that contains references
to arguments and can be implicitly converted to `~fmt::format_args`.
If ``format_str`` is a compile-time string then `make_args_checked` checks
its validity at compile time.
\endrst
*/
template <typename... Args, typename S, typename Char = char_t<S>>
FMT_INLINE auto make_args_checked(const S& format_str,
const remove_reference_t<Args>&... args)
-> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
static_assert(
detail::count<(
std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
detail::check_format_string<Args...>(format_str);
return {args...};
}
/** /**
\rst \rst
Returns a named argument to be used in a formatting function. Returns a named argument to be used in a formatting function.
@ -2668,49 +2647,9 @@ void check_format_string(S format_str) {
(void)invalid_format; (void)invalid_format;
} }
// Converts a compile-time string to basic_string_view.
template <typename Char, size_t N>
constexpr auto compile_string_to_view(const Char (&s)[N])
-> basic_string_view<Char> {
// Remove trailing NUL character if needed. Won't be present if this is used
// with a raw character array (i.e. not defined as a string).
return {s, N - (std::char_traits<Char>::to_int_type(s[N - 1]) == 0 ? 1 : 0)};
}
template <typename Char>
constexpr auto compile_string_to_view(std_string_view<Char> s)
-> basic_string_view<Char> {
return {s.data(), s.size()};
}
#define FMT_STRING_IMPL(s, base, explicit) \
[] { \
/* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
/* Use a macro-like name to avoid shadowing warnings. */ \
struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \
using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \
operator fmt::basic_string_view<char_type>() const { \
return fmt::detail::compile_string_to_view<char_type>(s); \
} \
}; \
return FMT_COMPILE_STRING(); \
}()
/**
\rst
Constructs a compile-time format string from a string literal *s*.
**Example**::
// A compile-time error because 'd' is an invalid specifier for strings.
std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
\endrst
*/
#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string, )
template <typename Char> template <typename Char>
void vformat_to( void vformat_to(
buffer<type_identity_t<Char>>& buf, basic_string_view<Char> format_str, buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args, basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
detail::locale_ref loc = {}); detail::locale_ref loc = {});
@ -2730,8 +2669,6 @@ struct formatter<T, Char,
detail::dynamic_format_specs<Char> specs_; detail::dynamic_format_specs<Char> specs_;
public: public:
FMT_CONSTEXPR formatter() = default;
// Parses format specifiers stopping either at the end of the range or at the // Parses format specifiers stopping either at the end of the range or at the
// terminating '}'. // terminating '}'.
template <typename ParseContext> template <typename ParseContext>
@ -2802,33 +2739,37 @@ struct formatter<T, Char,
-> decltype(ctx.out()); -> decltype(ctx.out());
}; };
template <typename Char> struct basic_runtime { basic_string_view<Char> str; };
template <typename Char, typename... Args> class basic_format_string { template <typename Char, typename... Args> class basic_format_string {
private: private:
basic_string_view<Char> str_; basic_string_view<Char> str_;
public: public:
#if FMT_COMPILE_TIME_CHECKS
template <size_t N>
consteval basic_format_string(const char (&s)[N]) : str_(s) {
if constexpr (detail::count_named_args<Args...>() == 0) {
using checker = detail::format_string_checker<char, detail::error_handler,
remove_cvref_t<Args>...>;
detail::parse_format_string<true>(string_view(s, N), checker(s, {}));
}
}
#endif
template <typename S, template <typename S,
FMT_ENABLE_IF( FMT_ENABLE_IF(
std::is_convertible<const S&, basic_string_view<Char>>::value)> std::is_convertible<const S&, basic_string_view<Char>>::value)>
basic_format_string(const S& s) : str_(s) { #if FMT_COMPILE_TIME_CHECKS
consteval
#endif
basic_format_string(const S& s)
: str_(s) {
static_assert( static_assert(
detail::count< detail::count<
(std::is_base_of<detail::view, remove_reference_t<Args>>::value && (std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0, std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed"); "passing views as lvalues is disallowed");
detail::check_format_string<Args...>(s); #if FMT_COMPILE_TIME_CHECKS
if constexpr (detail::count_named_args<Args...>() == 0) {
using checker = detail::format_string_checker<Char, detail::error_handler,
remove_cvref_t<Args>...>;
detail::parse_format_string<true>(str_, checker(s, {}));
} }
#else
detail::check_format_string<Args...>(s);
#endif
}
basic_format_string(basic_runtime<Char> r) : str_(r.str) {}
FMT_INLINE operator basic_string_view<Char>() const { return str_; } FMT_INLINE operator basic_string_view<Char>() const { return str_; }
}; };
@ -2836,9 +2777,16 @@ template <typename Char, typename... Args> class basic_format_string {
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc. // Workaround broken conversion on older gcc.
template <typename... Args> using format_string = string_view; template <typename... Args> using format_string = string_view;
template <typename S> auto runtime(const S& s) -> basic_string_view<char_t<S>> {
return s;
}
#else #else
template <typename... Args> template <typename... Args>
using format_string = basic_format_string<char, type_identity_t<Args>...>; using format_string = basic_format_string<char, type_identity_t<Args>...>;
// Creates a runtime format string.
template <typename S> auto runtime(const S& s) -> basic_runtime<char_t<S>> {
return {{s}};
}
#endif #endif
FMT_API auto vformat(string_view fmt, format_args args) -> std::string; FMT_API auto vformat(string_view fmt, format_args args) -> std::string;

View File

@ -2143,6 +2143,46 @@ FMT_CONSTEXPR void handle_dynamic_spec(int& value,
} }
} }
// Converts a compile-time string to basic_string_view.
template <typename Char, size_t N>
constexpr auto compile_string_to_view(const Char (&s)[N])
-> basic_string_view<Char> {
// Remove trailing NUL character if needed. Won't be present if this is used
// with a raw character array (i.e. not defined as a string).
return {s, N - (std::char_traits<Char>::to_int_type(s[N - 1]) == 0 ? 1 : 0)};
}
template <typename Char>
constexpr auto compile_string_to_view(std_string_view<Char> s)
-> basic_string_view<Char> {
return {s.data(), s.size()};
}
#define FMT_STRING_IMPL(s, base, explicit) \
[] { \
/* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
/* Use a macro-like name to avoid shadowing warnings. */ \
struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \
using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \
operator fmt::basic_string_view<char_type>() const { \
return fmt::detail::compile_string_to_view<char_type>(s); \
} \
}; \
return FMT_COMPILE_STRING(); \
}()
/**
\rst
Constructs a compile-time format string from a string literal *s*.
**Example**::
// A compile-time error because 'd' is an invalid specifier for strings.
std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
\endrst
*/
#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string, )
using format_func = void (*)(detail::buffer<char>&, int, const char*); using format_func = void (*)(detail::buffer<char>&, int, const char*);
FMT_API void format_error_code(buffer<char>& out, int error_code, FMT_API void format_error_code(buffer<char>& out, int error_code,
@ -2573,22 +2613,20 @@ FMT_MODULE_EXPORT_END
template <typename Char> template <typename Char>
void detail::vformat_to( void detail::vformat_to(
detail::buffer<type_identity_t<Char>>& buf, detail::buffer<Char>& buf, basic_string_view<Char> fmt,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args, basic_format_args<buffer_context<type_identity_t<Char>>> args,
detail::locale_ref loc) { detail::locale_ref loc) {
using iterator = typename buffer_context<Char>::iterator; using iterator = typename buffer_context<Char>::iterator;
auto out = buffer_appender<Char>(buf); auto out = buffer_appender<Char>(buf);
if (format_str.size() == 2 && equal2(format_str.data(), "{}")) { if (fmt.size() == 2 && equal2(fmt.data(), "{}")) {
auto arg = args.get(0); auto arg = args.get(0);
if (!arg) error_handler().on_error("argument not found"); if (!arg) error_handler().on_error("argument not found");
visit_format_arg(default_arg_formatter<iterator, Char>{out, args, loc}, visit_format_arg(default_arg_formatter<iterator, Char>{out, args, loc},
arg); arg);
return; return;
} }
format_handler<iterator, Char, buffer_context<Char>> h(out, format_str, args, format_handler<iterator, Char, buffer_context<Char>> h(out, fmt, args, loc);
loc); parse_format_string<false>(fmt, h);
parse_format_string<false>(format_str, h);
} }
#ifndef FMT_HEADER_ONLY #ifndef FMT_HEADER_ONLY
@ -2630,6 +2668,27 @@ template <typename OutputIt, typename Char = char>
using format_args_t FMT_DEPRECATED_ALIAS = using format_args_t FMT_DEPRECATED_ALIAS =
basic_format_args<basic_format_context<OutputIt, Char>>; basic_format_args<basic_format_context<OutputIt, Char>>;
/**
\rst
Constructs a `~fmt::format_arg_store` object that contains references
to arguments and can be implicitly converted to `~fmt::format_args`.
If ``fmt`` is a compile-time string then `make_args_checked` checks
its validity at compile time.
\endrst
*/
template <typename... Args, typename S, typename Char = char_t<S>>
FMT_INLINE auto make_args_checked(const S& fmt,
const remove_reference_t<Args>&... args)
-> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
static_assert(
detail::count<(
std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
detail::check_format_string<Args...>(fmt);
return {args...};
}
template <typename S, typename Char = char_t<S>, template <typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_string<S>::value)> FMT_ENABLE_IF(detail::is_string<S>::value)>
inline void vformat_to( inline void vformat_to(
@ -2736,9 +2795,9 @@ namespace detail {
template <typename Char> struct udl_formatter { template <typename Char> struct udl_formatter {
basic_string_view<Char> str; basic_string_view<Char> str;
template <typename... Args> template <typename... T>
std::basic_string<Char> operator()(Args&&... args) const { std::basic_string<Char> operator()(T&&... args) const {
return format(str, std::forward<Args>(args)...); return vformat(str, fmt::make_args_checked<T...>(str, args...));
} }
}; };
@ -2790,10 +2849,6 @@ constexpr auto operator"" _format(const char* s, size_t n)
-> detail::udl_formatter<char> { -> detail::udl_formatter<char> {
return {{s, n}}; return {{s, n}};
} }
constexpr auto operator"" _format(const wchar_t* s, size_t n)
-> detail::udl_formatter<wchar_t> {
return {{s, n}};
}
/** /**
\rst \rst

View File

@ -1,4 +1,4 @@
// Formatting library for C++ - experimental wchar_t support // Formatting library for C++ - optional wchar_t support
// //
// Copyright (c) 2012 - present, Victor Zverovich // Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved. // All rights reserved.
@ -12,7 +12,7 @@
#include "format.h" #include "format.h"
namespace fmt { FMT_BEGIN_NAMESPACE
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc. // Workaround broken conversion on older gcc.
@ -28,6 +28,13 @@ constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
return {args...}; return {args...};
} }
inline namespace literals {
constexpr auto operator"" _format(const wchar_t* s, size_t n)
-> detail::udl_formatter<wchar_t> {
return {{s, n}};
}
} // namespace literals
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) { inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
wmemory_buffer buffer; wmemory_buffer buffer;
detail::vformat_to(buffer, fmt, args); detail::vformat_to(buffer, fmt, args);
@ -48,6 +55,7 @@ void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) { template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), make_wformat_args(args...)); return vprint(wstring_view(fmt), make_wformat_args(args...));
} }
} // namespace fmt
FMT_END_NAMESPACE
#endif // FMT_WCHAR_H_ #endif // FMT_WCHAR_H_

View File

@ -9,6 +9,8 @@
#include "gtest-extra.h" // EXPECT_THROW_MSG #include "gtest-extra.h" // EXPECT_THROW_MSG
using fmt::runtime;
auto make_tm() -> std::tm { auto make_tm() -> std::tm {
auto time = std::tm(); auto time = std::tm();
time.tm_mday = 1; time.tm_mday = 1;
@ -52,7 +54,7 @@ TEST(chrono_test, grow_buffer) {
for (int i = 0; i < 30; ++i) s += "%c"; for (int i = 0; i < 30; ++i) s += "%c";
s += "}\n"; s += "}\n";
auto t = std::time(nullptr); auto t = std::time(nullptr);
fmt::format(s, *std::localtime(&t)); fmt::format(fmt::runtime(s), *std::localtime(&t));
} }
TEST(chrono_test, format_to_empty_container) { TEST(chrono_test, format_to_empty_container) {
@ -197,25 +199,41 @@ TEST(chrono_test, format_specs) {
TEST(chrono_test, invalid_specs) { TEST(chrono_test, invalid_specs) {
auto sec = std::chrono::seconds(0); auto sec = std::chrono::seconds(0);
EXPECT_THROW_MSG(fmt::format(+"{:%a}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%a}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format(+"{:%A}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format(+"{:%c}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%A}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format(+"{:%x}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format(+"{:%Ex}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%c}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format(+"{:%X}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format(+"{:%EX}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%x}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format(+"{:%D}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format(+"{:%F}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%Ex}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format(+"{:%Ec}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format(+"{:%w}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%X}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format(+"{:%u}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format(+"{:%b}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%EX}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format(+"{:%B}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format(+"{:%z}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%D}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format(+"{:%Z}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format(+"{:%Eq}", sec), fmt::format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:%F}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Ec}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%w}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%u}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%b}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%B}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%z}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Z}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Eq}"), sec), fmt::format_error,
"invalid format"); "invalid format");
EXPECT_THROW_MSG(fmt::format(+"{:%Oq}", sec), fmt::format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
"invalid format"); "invalid format");
} }
@ -274,7 +292,7 @@ TEST(chrono_test, format_default_fp) {
} }
TEST(chrono_test, format_precision) { TEST(chrono_test, format_precision) {
EXPECT_THROW_MSG(fmt::format(+"{:.2}", std::chrono::seconds(42)), EXPECT_THROW_MSG(fmt::format(runtime("{:.2}"), std::chrono::seconds(42)),
fmt::format_error, fmt::format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
@ -301,7 +319,7 @@ TEST(chrono_test, format_simple_q) {
} }
TEST(chrono_test, format_precision_q) { TEST(chrono_test, format_precision_q) {
EXPECT_THROW_MSG(fmt::format(+"{:.2%Q %q}", std::chrono::seconds(42)), EXPECT_THROW_MSG(fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)),
fmt::format_error, fmt::format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234))); EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234)));
@ -318,12 +336,12 @@ TEST(chrono_test, format_full_specs_q) {
} }
TEST(chrono_test, invalid_width_id) { TEST(chrono_test, invalid_width_id) {
EXPECT_THROW(fmt::format(+"{:{o}", std::chrono::seconds(0)), EXPECT_THROW(fmt::format(runtime("{:{o}"), std::chrono::seconds(0)),
fmt::format_error); fmt::format_error);
} }
TEST(chrono_test, invalid_colons) { TEST(chrono_test, invalid_colons) {
EXPECT_THROW(fmt::format(+"{0}=:{0::", std::chrono::seconds(0)), EXPECT_THROW(fmt::format(runtime("{0}=:{0::"), std::chrono::seconds(0)),
fmt::format_error); fmt::format_error);
} }

View File

@ -29,6 +29,7 @@
using fmt::basic_memory_buffer; using fmt::basic_memory_buffer;
using fmt::format_error; using fmt::format_error;
using fmt::memory_buffer; using fmt::memory_buffer;
using fmt::runtime;
using fmt::string_view; using fmt::string_view;
using fmt::detail::max_value; using fmt::detail::max_value;
@ -372,10 +373,12 @@ TEST(format_test, escape) {
} }
TEST(format_test, unmatched_braces) { TEST(format_test, unmatched_braces) {
EXPECT_THROW_MSG(fmt::format(+"{"), format_error, "invalid format string"); EXPECT_THROW_MSG(fmt::format(runtime("{")), format_error,
EXPECT_THROW_MSG(fmt::format(+"}"), format_error, "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("}")), format_error,
"unmatched '}' in format string"); "unmatched '}' in format string");
EXPECT_THROW_MSG(fmt::format(+"{0{}"), format_error, "invalid format string"); EXPECT_THROW_MSG(fmt::format(runtime("{0{}")), format_error,
"invalid format string");
} }
TEST(format_test, no_args) { EXPECT_EQ("test", fmt::format("test")); } TEST(format_test, no_args) { EXPECT_EQ("test", fmt::format("test")); }
@ -391,38 +394,44 @@ TEST(format_test, args_in_different_positions) {
} }
TEST(format_test, arg_errors) { TEST(format_test, arg_errors) {
EXPECT_THROW_MSG(fmt::format(+"{"), format_error, "invalid format string"); EXPECT_THROW_MSG(fmt::format(runtime("{")), format_error,
EXPECT_THROW_MSG(fmt::format(+"{?}"), format_error, "invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(+"{0"), format_error, "invalid format string"); EXPECT_THROW_MSG(fmt::format(runtime("{?}")), format_error,
EXPECT_THROW_MSG(fmt::format(+"{0}"), format_error, "argument not found"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(+"{00}", 42), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0")), format_error,
"invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0}")), format_error,
"argument not found");
EXPECT_THROW_MSG(fmt::format(runtime("{00}"), 42), format_error,
"invalid format string"); "invalid format string");
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{%u", INT_MAX); safe_sprintf(format_str, "{%u", INT_MAX);
EXPECT_THROW_MSG(fmt::format(+format_str), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error,
"invalid format string"); "invalid format string");
safe_sprintf(format_str, "{%u}", INT_MAX); safe_sprintf(format_str, "{%u}", INT_MAX);
EXPECT_THROW_MSG(fmt::format(+format_str), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error,
"argument not found"); "argument not found");
safe_sprintf(format_str, "{%u", INT_MAX + 1u); safe_sprintf(format_str, "{%u", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(+format_str), format_error, "number is too big"); EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error,
"number is too big");
safe_sprintf(format_str, "{%u}", INT_MAX + 1u); safe_sprintf(format_str, "{%u}", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(+format_str), format_error, "number is too big"); EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error,
"number is too big");
} }
template <int N> struct test_format { template <int N> struct test_format {
template <typename... Args> template <typename... T>
static std::string format(fmt::string_view format_str, const Args&... args) { static std::string format(fmt::string_view fmt, const T&... args) {
return test_format<N - 1>::format(format_str, N - 1, args...); return test_format<N - 1>::format(fmt, N - 1, args...);
} }
}; };
template <> struct test_format<0> { template <> struct test_format<0> {
template <typename... Args> template <typename... T>
static std::string format(fmt::string_view format_str, const Args&... args) { static std::string format(fmt::string_view fmt, const T&... args) {
return fmt::format(format_str, args...); return fmt::format(runtime(fmt), args...);
} }
}; };
@ -452,22 +461,25 @@ TEST(format_test, named_arg) {
fmt::arg("i", 0), fmt::arg("j", 0), fmt::arg("k", 0), fmt::arg("i", 0), fmt::arg("j", 0), fmt::arg("k", 0),
fmt::arg("l", 0), fmt::arg("m", 0), fmt::arg("n", 0), fmt::arg("l", 0), fmt::arg("m", 0), fmt::arg("n", 0),
fmt::arg("o", 0), fmt::arg("p", 0))); fmt::arg("o", 0), fmt::arg("p", 0)));
EXPECT_THROW_MSG(fmt::format(+"{a}"), format_error, "argument not found"); EXPECT_THROW_MSG(fmt::format(runtime("{a}")), format_error,
EXPECT_THROW_MSG(fmt::format(+"{a}", 42), format_error, "argument not found"); "argument not found");
EXPECT_THROW_MSG(fmt::format(runtime("{a}"), 42), format_error,
"argument not found");
} }
TEST(format_test, auto_arg_index) { TEST(format_test, auto_arg_index) {
EXPECT_EQ("abc", fmt::format("{}{}{}", 'a', 'b', 'c')); EXPECT_EQ("abc", fmt::format("{}{}{}", 'a', 'b', 'c'));
EXPECT_THROW_MSG(fmt::format(+"{0}{}", 'a', 'b'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0}{}"), 'a', 'b'), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(+"{}{0}", 'a', 'b'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{}{0}"), 'a', 'b'), format_error,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
EXPECT_EQ("1.2", fmt::format("{:.{}}", 1.2345, 2)); EXPECT_EQ("1.2", fmt::format("{:.{}}", 1.2345, 2));
EXPECT_THROW_MSG(fmt::format(+"{0}:.{}", 1.2345, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0}:.{}"), 1.2345, 2), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(+"{:.{0}}", 1.2345, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:.{0}}"), 1.2345, 2), format_error,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG(fmt::format(+"{}"), format_error, "argument not found"); EXPECT_THROW_MSG(fmt::format(runtime("{}")), format_error,
"argument not found");
} }
TEST(format_test, empty_specs) { EXPECT_EQ("42", fmt::format("{0:}", 42)); } TEST(format_test, empty_specs) { EXPECT_EQ("42", fmt::format("{0:}", 42)); }
@ -524,9 +536,9 @@ TEST(format_test, center_align) {
} }
TEST(format_test, fill) { TEST(format_test, fill) {
EXPECT_THROW_MSG(fmt::format(+"{0:{<5}", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{<5}"), 'c'), format_error,
"invalid fill character '{'"); "invalid fill character '{'");
EXPECT_THROW_MSG(fmt::format(+"{0:{<5}}", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{<5}}"), 'c'), format_error,
"invalid fill character '{'"); "invalid fill character '{'");
EXPECT_EQ("**42", fmt::format("{0:*>4}", 42)); EXPECT_EQ("**42", fmt::format("{0:*>4}", 42));
EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42)); EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42));
@ -545,31 +557,31 @@ TEST(format_test, fill) {
EXPECT_EQ(std::string("\0\0\0*", 4), EXPECT_EQ(std::string("\0\0\0*", 4),
fmt::format(string_view("{:\0>4}", 6), '*')); fmt::format(string_view("{:\0>4}", 6), '*'));
EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42)); EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42));
EXPECT_THROW_MSG(fmt::format(+"{:\x80\x80\x80\x80\x80>}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0),
"missing '}' in format string"); format_error, "missing '}' in format string");
} }
TEST(format_test, plus_sign) { TEST(format_test, plus_sign) {
EXPECT_EQ("+42", fmt::format("{0:+}", 42)); EXPECT_EQ("+42", fmt::format("{0:+}", 42));
EXPECT_EQ("-42", fmt::format("{0:+}", -42)); EXPECT_EQ("-42", fmt::format("{0:+}", -42));
EXPECT_EQ("+42", fmt::format("{0:+}", 42)); EXPECT_EQ("+42", fmt::format("{0:+}", 42));
EXPECT_THROW_MSG(fmt::format(+"{0:+}", 42u), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 42u), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("+42", fmt::format("{0:+}", 42l)); EXPECT_EQ("+42", fmt::format("{0:+}", 42l));
EXPECT_THROW_MSG(fmt::format(+"{0:+}", 42ul), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 42ul), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("+42", fmt::format("{0:+}", 42ll)); EXPECT_EQ("+42", fmt::format("{0:+}", 42ll));
EXPECT_THROW_MSG(fmt::format(+"{0:+}", 42ull), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 42ull), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("+42", fmt::format("{0:+}", 42.0)); EXPECT_EQ("+42", fmt::format("{0:+}", 42.0));
EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l)); EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l));
EXPECT_THROW_MSG(fmt::format(+"{0:+", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:+"), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(+"{0:+}", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(+"{0:+}", "abc"), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{0:+}", reinterpret_cast<void*>(0x42)), EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -577,23 +589,23 @@ TEST(format_test, minus_sign) {
EXPECT_EQ("42", fmt::format("{0:-}", 42)); EXPECT_EQ("42", fmt::format("{0:-}", 42));
EXPECT_EQ("-42", fmt::format("{0:-}", -42)); EXPECT_EQ("-42", fmt::format("{0:-}", -42));
EXPECT_EQ("42", fmt::format("{0:-}", 42)); EXPECT_EQ("42", fmt::format("{0:-}", 42));
EXPECT_THROW_MSG(fmt::format(+"{0:-}", 42u), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 42u), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("42", fmt::format("{0:-}", 42l)); EXPECT_EQ("42", fmt::format("{0:-}", 42l));
EXPECT_THROW_MSG(fmt::format(+"{0:-}", 42ul), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 42ul), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("42", fmt::format("{0:-}", 42ll)); EXPECT_EQ("42", fmt::format("{0:-}", 42ll));
EXPECT_THROW_MSG(fmt::format(+"{0:-}", 42ull), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 42ull), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("42", fmt::format("{0:-}", 42.0)); EXPECT_EQ("42", fmt::format("{0:-}", 42.0));
EXPECT_EQ("42", fmt::format("{0:-}", 42.0l)); EXPECT_EQ("42", fmt::format("{0:-}", 42.0l));
EXPECT_THROW_MSG(fmt::format(+"{0:-", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:-"), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(+"{0:-}", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(+"{0:-}", "abc"), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{0:-}", reinterpret_cast<void*>(0x42)), EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -601,23 +613,23 @@ TEST(format_test, space_sign) {
EXPECT_EQ(" 42", fmt::format("{0: }", 42)); EXPECT_EQ(" 42", fmt::format("{0: }", 42));
EXPECT_EQ("-42", fmt::format("{0: }", -42)); EXPECT_EQ("-42", fmt::format("{0: }", -42));
EXPECT_EQ(" 42", fmt::format("{0: }", 42)); EXPECT_EQ(" 42", fmt::format("{0: }", 42));
EXPECT_THROW_MSG(fmt::format(+"{0: }", 42u), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 42u), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ(" 42", fmt::format("{0: }", 42l)); EXPECT_EQ(" 42", fmt::format("{0: }", 42l));
EXPECT_THROW_MSG(fmt::format(+"{0: }", 42ul), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 42ul), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ(" 42", fmt::format("{0: }", 42ll)); EXPECT_EQ(" 42", fmt::format("{0: }", 42ll));
EXPECT_THROW_MSG(fmt::format(+"{0: }", 42ull), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 42ull), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ(" 42", fmt::format("{0: }", 42.0)); EXPECT_EQ(" 42", fmt::format("{0: }", 42.0));
EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l)); EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l));
EXPECT_THROW_MSG(fmt::format(+"{0: ", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0: "), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(+"{0: }", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(+"{0: }", "abc"), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{0: }", reinterpret_cast<void*>(0x42)), EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -668,13 +680,13 @@ TEST(format_test, hash_flag) {
EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01)); EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01));
EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5)); EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5));
EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.5)); EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.5));
EXPECT_THROW_MSG(fmt::format(+"{0:#", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:#"), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(+"{0:#}", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(+"{0:#}", "abc"), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{0:#}", reinterpret_cast<void*>(0x42)), EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -688,13 +700,14 @@ TEST(format_test, zero_flag) {
EXPECT_EQ("00042", fmt::format("{0:05}", 42ull)); EXPECT_EQ("00042", fmt::format("{0:05}", 42ull));
EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0)); EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0));
EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0l)); EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0l));
EXPECT_THROW_MSG(fmt::format(+"{0:0", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:0"), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(+"{0:05}", 'c'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(+"{0:05}", "abc"), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{0:05}", reinterpret_cast<void*>(0x42)), EXPECT_THROW_MSG(
fmt::format(runtime("{0:05}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -702,19 +715,19 @@ TEST(format_test, width) {
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{0:%u", UINT_MAX); safe_sprintf(format_str, "{0:%u", UINT_MAX);
increment(format_str + 3); increment(format_str + 3);
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
size_t size = std::strlen(format_str); size_t size = std::strlen(format_str);
format_str[size] = '}'; format_str[size] = '}';
format_str[size + 1] = 0; format_str[size + 1] = 0;
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
safe_sprintf(format_str, "{0:%u", INT_MAX + 1u); safe_sprintf(format_str, "{0:%u", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
safe_sprintf(format_str, "{0:%u}", INT_MAX + 1u); safe_sprintf(format_str, "{0:%u}", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
EXPECT_EQ(" -42", fmt::format("{0:4}", -42)); EXPECT_EQ(" -42", fmt::format("{0:4}", -42));
EXPECT_EQ(" 42", fmt::format("{0:5}", 42u)); EXPECT_EQ(" 42", fmt::format("{0:5}", 42u));
@ -739,47 +752,47 @@ TEST(format_test, runtime_width) {
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{0:{%u", UINT_MAX); safe_sprintf(format_str, "{0:{%u", UINT_MAX);
increment(format_str + 4); increment(format_str + 4);
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
size_t size = std::strlen(format_str); size_t size = std::strlen(format_str);
format_str[size] = '}'; format_str[size] = '}';
format_str[size + 1] = 0; format_str[size + 1] = 0;
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
format_str[size + 1] = '}'; format_str[size + 1] = '}';
format_str[size + 2] = 0; format_str[size + 2] = 0;
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
EXPECT_THROW_MSG(fmt::format(+"{0:{", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(+"{0:{}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{}"), 0), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(+"{0:{?}}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{?}}"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0), format_error,
"argument not found"); "argument not found");
EXPECT_THROW_MSG(fmt::format(+"{0:{0:}}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{0:}}"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, -1), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, -1), format_error,
"negative width"); "negative width");
EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, (INT_MAX + 1u)), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)),
"number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, -1l), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, -1l), format_error,
"negative width"); "negative width");
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
long value = INT_MAX; long value = INT_MAX;
EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, (value + 1)), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, (value + 1)),
"number is too big"); format_error, "number is too big");
} }
EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, (INT_MAX + 1ul)), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)),
"number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, '0'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, '0'), format_error,
"width is not integer"); "width is not integer");
EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, 0.0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error,
"width is not integer"); "width is not integer");
EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42, 4)); EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42, 4));
@ -800,53 +813,53 @@ TEST(format_test, precision) {
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{0:.%u", UINT_MAX); safe_sprintf(format_str, "{0:.%u", UINT_MAX);
increment(format_str + 4); increment(format_str + 4);
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
size_t size = std::strlen(format_str); size_t size = std::strlen(format_str);
format_str[size] = '}'; format_str[size] = '}';
format_str[size + 1] = 0; format_str[size + 1] = 0;
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
safe_sprintf(format_str, "{0:.%u", INT_MAX + 1u); safe_sprintf(format_str, "{0:.%u", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
safe_sprintf(format_str, "{0:.%u}", INT_MAX + 1u); safe_sprintf(format_str, "{0:.%u}", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
EXPECT_THROW_MSG(fmt::format(+"{0:.", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:."), 0), format_error,
"missing precision specifier"); "missing precision specifier");
EXPECT_THROW_MSG(fmt::format(+"{0:.}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.}"), 0), format_error,
"missing precision specifier"); "missing precision specifier");
EXPECT_THROW_MSG(fmt::format(+"{0:.2", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2"), 0), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42u), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42u), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42u), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42u), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42l), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42l), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42l), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42l), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42ul), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42ul), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42ul), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42ul), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42ll), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42ll), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42ll), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42ll), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42ull), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42ull), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42ull), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42ull), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:3.0}", 'x'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:3.0}"), 'x'), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345)); EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345));
EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l)); EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l));
@ -899,13 +912,14 @@ TEST(format_test, precision) {
EXPECT_EQ("1e+01", fmt::format("{:.0e}", 9.5)); EXPECT_EQ("1e+01", fmt::format("{:.0e}", 9.5));
EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34)); EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34));
EXPECT_THROW_MSG(fmt::format(+"{0:.2}", reinterpret_cast<void*>(0xcafe)), EXPECT_THROW_MSG(
format_error, fmt::format(runtime("{0:.2}"), reinterpret_cast<void*>(0xcafe)),
"precision not allowed for this argument type"); format_error, "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", reinterpret_cast<void*>(0xcafe)), EXPECT_THROW_MSG(
format_error, fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
"precision not allowed for this argument type"); format_error, "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{:.{}e}", 42.0, fmt::detail::max_value<int>()), EXPECT_THROW_MSG(
fmt::format(runtime("{:.{}e}"), 42.0, fmt::detail::max_value<int>()),
format_error, "number is too big"); format_error, "number is too big");
EXPECT_EQ("st", fmt::format("{0:.2}", "str")); EXPECT_EQ("st", fmt::format("{0:.2}", "str"));
@ -915,85 +929,85 @@ TEST(format_test, runtime_precision) {
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{0:.{%u", UINT_MAX); safe_sprintf(format_str, "{0:.{%u", UINT_MAX);
increment(format_str + 5); increment(format_str + 5);
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
size_t size = std::strlen(format_str); size_t size = std::strlen(format_str);
format_str[size] = '}'; format_str[size] = '}';
format_str[size + 1] = 0; format_str[size + 1] = 0;
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
format_str[size + 1] = '}'; format_str[size + 1] = '}';
format_str[size + 2] = 0; format_str[size + 2] = 0;
EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
EXPECT_THROW_MSG(fmt::format(+"{0:.{", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(+"{0:.{}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{}"), 0), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(+"{0:.{?}}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{?}}"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}", 0, 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}"), 0, 0), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0), format_error,
"argument not found"); "argument not found");
EXPECT_THROW_MSG(fmt::format(+"{0:.{0:}}", 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{0:}}"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, -1), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, -1), format_error,
"negative precision"); "negative precision");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, (INT_MAX + 1u)), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1u)),
"number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, -1l), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, -1l), format_error,
"negative precision"); "negative precision");
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
long value = INT_MAX; long value = INT_MAX;
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, (value + 1)), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, (value + 1)),
"number is too big"); format_error, "number is too big");
} }
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, (INT_MAX + 1ul)), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1ul)),
"number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, '0'), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, '0'), format_error,
"precision is not integer"); "precision is not integer");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, 0.0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, 0.0), format_error,
"precision is not integer"); "precision is not integer");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42u, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42u, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42u, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42u, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42l, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42l, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42l, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42l, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42ul, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42ul, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42ul, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42ul, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42ll, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42ll, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42ll, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42ll, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42ull, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42ull, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42ull, 2), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42ull, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(+"{0:3.{1}}", 'x', 0), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:3.{1}}"), 'x', 0), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2)); EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2));
EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l)); EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l));
EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", reinterpret_cast<void*>(0xcafe), 2),
format_error,
"precision not allowed for this argument type");
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
fmt::format(+"{0:.{1}f}", reinterpret_cast<void*>(0xcafe), 2), fmt::format(runtime("{0:.{1}}"), reinterpret_cast<void*>(0xcafe), 2),
format_error, "precision not allowed for this argument type");
EXPECT_THROW_MSG(
fmt::format(runtime("{0:.{1}f}"), reinterpret_cast<void*>(0xcafe), 2),
format_error, "precision not allowed for this argument type"); format_error, "precision not allowed for this argument type");
EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2)); EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2));
@ -1026,13 +1040,14 @@ void check_unknown_types(const T& value, const char* types, const char*) {
if (std::strchr(types, c) || std::strchr(special, c) || !c) continue; if (std::strchr(types, c) || std::strchr(special, c) || !c) continue;
safe_sprintf(format_str, "{0:10%c}", c); safe_sprintf(format_str, "{0:10%c}", c);
const char* message = "invalid type specifier"; const char* message = "invalid type specifier";
EXPECT_THROW_MSG(fmt::format(+format_str, value), format_error, message) EXPECT_THROW_MSG(fmt::format(runtime(format_str), value), format_error,
message)
<< format_str << " " << message; << format_str << " " << message;
} }
} }
TEST(format_test, format_int) { TEST(format_test, format_int) {
EXPECT_THROW_MSG(fmt::format(+"{0:v", 42), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:v"), 42), format_error,
"missing '}' in format string"); "missing '}' in format string");
check_unknown_types(42, "bBdoxXnLc", "integer"); check_unknown_types(42, "bBdoxXnLc", "integer");
EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x'))); EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x')));
@ -1305,7 +1320,8 @@ TEST(format_test, format_char) {
int n = 'x'; int n = 'x';
for (const char* type = types + 1; *type; ++type) { for (const char* type = types + 1; *type; ++type) {
std::string format_str = fmt::format("{{:{}}}", *type); std::string format_str = fmt::format("{{:{}}}", *type);
EXPECT_EQ(fmt::format(format_str, n), fmt::format(format_str, 'x')) EXPECT_EQ(fmt::format(runtime(format_str), n),
fmt::format(runtime(format_str), 'x'))
<< format_str; << format_str;
} }
EXPECT_EQ(fmt::format("{:02X}", n), fmt::format("{:02X}", 'x')); EXPECT_EQ(fmt::format("{:02X}", n), fmt::format("{:02X}", 'x'));
@ -1333,7 +1349,8 @@ TEST(format_test, format_cstring) {
EXPECT_EQ("test", fmt::format("{0:s}", "test")); EXPECT_EQ("test", fmt::format("{0:s}", "test"));
char nonconst[] = "nonconst"; char nonconst[] = "nonconst";
EXPECT_EQ("nonconst", fmt::format("{0}", nonconst)); EXPECT_EQ("nonconst", fmt::format("{0}", nonconst));
EXPECT_THROW_MSG(fmt::format(+"{0}", static_cast<const char*>(nullptr)), EXPECT_THROW_MSG(
fmt::format(runtime("{0}"), static_cast<const char*>(nullptr)),
format_error, "string pointer is null"); format_error, "string pointer is null");
} }
@ -1480,8 +1497,8 @@ template <> struct formatter<Answer> : formatter<int> {
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(format_test, format_custom) { TEST(format_test, format_custom) {
EXPECT_THROW_MSG(fmt::format(+"{:s}", date(2012, 12, 9)), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:s}"), date(2012, 12, 9)),
"unknown format specifier"); format_error, "unknown format specifier");
EXPECT_EQ("42", fmt::format("{0}", Answer())); EXPECT_EQ("42", fmt::format("{0}", Answer()));
EXPECT_EQ("0042", fmt::format("{:04}", Answer())); EXPECT_EQ("0042", fmt::format("{:04}", Answer()));
} }
@ -1513,7 +1530,6 @@ TEST(format_test, format_examples) {
EXPECT_EQ("The answer is 42", message); EXPECT_EQ("The answer is 42", message);
EXPECT_EQ("42", fmt::format("{}", 42)); EXPECT_EQ("42", fmt::format("{}", 42));
EXPECT_EQ("42", fmt::format(std::string("{}"), 42));
memory_buffer out; memory_buffer out;
format_to(out, "The answer is {}.", 42); format_to(out, "The answer is {}.", 42);
@ -1566,7 +1582,7 @@ TEST(format_test, format_examples) {
fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}", 42)); fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}", 42));
EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42)); EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42));
EXPECT_THROW_MSG(fmt::format(+"The answer is {:d}", "forty-two"), EXPECT_THROW_MSG(fmt::format(runtime("The answer is {:d}"), "forty-two"),
format_error, "invalid type specifier"); format_error, "invalid type specifier");
EXPECT_EQ(L"Cyrillic letter \x42e", EXPECT_EQ(L"Cyrillic letter \x42e",
@ -1741,10 +1757,10 @@ TEST(format_test, custom_format_compile_time_string) {
using namespace fmt::literals; using namespace fmt::literals;
TEST(format_test, format_udl) { TEST(format_test, format_udl) {
auto udl_format = "{}c{}"_format("ab", 1); EXPECT_EQ("{}c{}"_format("ab", 1), fmt::format("{}c{}", "ab", 1));
EXPECT_EQ(fmt::format("{}c{}", "ab", 1), udl_format); EXPECT_EQ("foo"_format(), "foo");
auto udl_format_w = L"{}c{}"_format(L"ab", 1); EXPECT_EQ("{0:10}"_format(42), " 42");
EXPECT_EQ(fmt::format(L"{}c{}", L"ab", 1), udl_format_w); EXPECT_EQ("{}"_format(date(2015, 10, 21)), "2015-10-21");
} }
TEST(format_test, named_arg_udl) { TEST(format_test, named_arg_udl) {
@ -1762,15 +1778,6 @@ TEST(format_test, named_arg_udl) {
fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)), fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)),
udl_a_w); udl_a_w);
} }
TEST(format_test, udl_template) {
EXPECT_EQ("foo", "foo"_format());
EXPECT_EQ(" 42", "{0:10}"_format(42));
}
TEST(format_test, udl_pass_user_defined_object_as_lvalue) {
EXPECT_EQ("2015-10-21", "{}"_format(date(2015, 10, 21)));
}
#endif // FMT_USE_USER_DEFINED_LITERALS #endif // FMT_USE_USER_DEFINED_LITERALS
TEST(format_test, enum) { EXPECT_EQ("0", fmt::format("{}", foo)); } TEST(format_test, enum) { EXPECT_EQ("0", fmt::format("{}", foo)); }
@ -1815,21 +1822,21 @@ TEST(format_test, dynamic_formatter) {
EXPECT_EQ("42", fmt::format("{:d}", num)); EXPECT_EQ("42", fmt::format("{:d}", num));
EXPECT_EQ("foo", fmt::format("{:s}", str)); EXPECT_EQ("foo", fmt::format("{:s}", str));
EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4)); EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4));
EXPECT_THROW_MSG(fmt::format(+"{0:{}}", num), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:{}}"), num), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(+"{:{0}}", num), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:{0}}"), num), format_error,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG(fmt::format(+"{:+}", str), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:+}"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{:-}", str), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:-}"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{: }", str), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{: }"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{:#}", str), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:#}"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{:0}", str), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:0}"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{:.2}", num), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:.2}"), num), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
} }

View File

@ -149,9 +149,9 @@ template <class charT> struct formatter<std::complex<double>, charT> {
"{:" + specs + "}", c.imag()); "{:" + specs + "}", c.imag());
auto fill_align_width = std::string(); auto fill_align_width = std::string();
if (specs_.width > 0) fill_align_width = fmt::format(">{}", specs_.width); if (specs_.width > 0) fill_align_width = fmt::format(">{}", specs_.width);
return format_to( return format_to(ctx.out(), runtime("{:" + fill_align_width + "}"),
ctx.out(), "{:" + fill_align_width + "}", c.real() != 0 ? fmt::format("({}+{}i)", real, imag)
fmt::format(c.real() != 0 ? "({0}+{1}i)" : "{1}i", real, imag)); : fmt::format("{}i", imag));
} }
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@ -7,6 +7,8 @@
#include "fmt/format.h" #include "fmt/format.h"
using fmt::runtime;
struct test {}; struct test {};
// Test that there is no issues with specializations when fmt/ostream.h is // Test that there is no issues with specializations when fmt/ostream.h is
@ -74,15 +76,15 @@ TEST(ostream_test, format_specs) {
EXPECT_EQ(" def", fmt::format("{0:>5}", test_string("def"))); EXPECT_EQ(" def", fmt::format("{0:>5}", test_string("def")));
EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def"))); EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def"))); EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
EXPECT_THROW_MSG(fmt::format(+"{0:+}", test_string()), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), test_string()), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{0:-}", test_string()), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), test_string()), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{0: }", test_string()), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), test_string()), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{0:#}", test_string()), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), test_string()), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(+"{0:05}", test_string()), format_error, EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), test_string()), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test"))); EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
EXPECT_EQ("test ", fmt::format("{0:{1}}", test_string("test"), 13)); EXPECT_EQ("test ", fmt::format("{0:{1}}", test_string("test"), 13));

View File

@ -9,6 +9,13 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
#if FMT_USE_USER_DEFINED_LITERALS
TEST(format_test, format_udl) {
using namespace fmt::literals;
EXPECT_EQ(L"{}c{}"_format(L"ab", 1), fmt::format(L"{}c{}", L"ab", 1));
}
#endif
TEST(wchar_test, print) { TEST(wchar_test, print) {
// Check that the wide print overload compiles. // Check that the wide print overload compiles.
if (fmt::detail::const_check(false)) fmt::print(L"test"); if (fmt::detail::const_check(false)) fmt::print(L"test");