diff --git a/include/fmt/format.h b/include/fmt/format.h index 6dffee02..4fceeb0c 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1708,6 +1708,7 @@ typedef basic_format_specs format_specs; namespace internal { struct error_handler { + // This function is intentionally not constexpr to give a compile-time error. void on_error(const char *message) { FMT_THROW(format_error(message)); } @@ -2586,10 +2587,11 @@ constexpr const Char *parse_format_specs(parse_context &ctx) { return f.parse(ctx); } -template -struct format_string_checker { +template +class format_string_checker : public ErrorHandler { public: - explicit constexpr format_string_checker(const Char *end) : end_(end) {} + explicit constexpr format_string_checker(ErrorHandler eh, const Char *end) + : ErrorHandler(std::move(eh)), end_(end) {} constexpr void on_text(const Char *, const Char *) {} @@ -2610,13 +2612,10 @@ struct format_string_checker { return parse_funcs_[arg_index_](ctx); } - // This function is intentionally not constexpr to give a compile-time error. - void on_error(const char *); - private: constexpr void check_arg_index() { if (arg_index_ < 0 || arg_index_ >= sizeof...(Args)) - on_error("argument index out of range"); + this->on_error("argument index out of range"); } // Format specifier parsing function. @@ -2629,10 +2628,12 @@ struct format_string_checker { }; }; -template -constexpr bool check_format_string(basic_string_view s) { - format_string_checker checker(s.end()); - internal::parse_format_string(s.begin(), checker); +template +constexpr bool check_format_string( + basic_string_view s, ErrorHandler eh = ErrorHandler()) { + format_string_checker + checker(std::move(eh), s.end()); + parse_format_string(s.begin(), checker); return true; } @@ -3538,7 +3539,8 @@ template inline typename std::enable_if< std::is_base_of::value, std::string>::type format(String format_str, const Args & ... args) { - constexpr bool invalid_format = internal::check_format_string( + constexpr bool invalid_format = + internal::check_format_string( string_view(format_str.value(), format_str.size())); return vformat(format_str.value(), make_args(args...)); } @@ -3919,7 +3921,8 @@ class udl_formatter { template std::basic_string operator()(const Args &... args) const { constexpr Char s[] = {CHARS..., '\0'}; - constexpr bool invalid_format = check_format_string( + constexpr bool invalid_format = + check_format_string( basic_string_view(s, sizeof...(CHARS))); return format(s, args...); } diff --git a/test/format-test.cc b/test/format-test.cc index 585e7752..b024d080 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1817,3 +1817,45 @@ TEST(FormatTest, UdlTemplate) { EXPECT_EQ(" 42", "{0:10}"_format(42)); EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42)); } + +struct test_error_handler { + const char *&error; + + constexpr void on_error(const char *message) { error = message; } +}; + +constexpr size_t len(const char *s) { + size_t len = 0; + while (*s++) + ++len; + return len; +} + +constexpr bool eq(const char *s1, const char *s2) { + if (!s1 && !s2) + return true; + while (*s1 && *s1 == *s2) { + ++s1; + ++s2; + } + return *s1 == *s2; +} + +template +constexpr bool test_error(const char *fmt, const char *expected_error) { + const char *actual_error = nullptr; + test_error_handler eh{actual_error}; + using ref = std::reference_wrapper; + fmt::internal::check_format_string( + string_view(fmt, len(fmt)), eh); + return eq(actual_error, expected_error); +} + +#define EXPECT_ERROR(fmt, error, ...) \ + static_assert(test_error<__VA_ARGS__>(fmt, error), "") + +TEST(FormatTest, FormatStringErrors) { + EXPECT_ERROR("foo", nullptr); + EXPECT_ERROR("}", "unmatched '}' in format string"); + EXPECT_ERROR("{0:s", "unknown format specifier", Date); +}