Skip to content

Commit bcc20b2

Browse files
committed
Implement compile-time checks by default
1 parent befd7d4 commit bcc20b2

File tree

12 files changed

+258
-223
lines changed

12 files changed

+258
-223
lines changed

.github/workflows/linux.yml

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ jobs:
1717
std: 14
1818
- cxx: g++-10
1919
std: 17
20+
- cxx: g++-10
21+
std: 20
22+
cxxflags: -DFMT_COMPILE_TIME_CHECKS=1
2023
- cxx: clang++-9
2124
std: 11
2225
- cxx: clang++-9
@@ -36,6 +39,7 @@ jobs:
3639
working-directory: ${{runner.workspace}}/build
3740
env:
3841
CXX: ${{matrix.cxx}}
42+
CXXFLAGS: ${{matrix.cxxflags}}
3943
run: |
4044
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} \
4145
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \

include/fmt/chrono.h

+31-28
Original file line numberDiff line numberDiff line change
@@ -404,20 +404,20 @@ struct formatter<std::chrono::time_point<std::chrono::system_clock>, Char>
404404
};
405405

406406
template <typename Char> struct formatter<std::tm, Char> {
407-
template <typename ParseContext>
408-
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
407+
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
409408
auto it = ctx.begin();
410409
if (it != ctx.end() && *it == ':') ++it;
411410
auto end = it;
412411
while (end != ctx.end() && *end != '}') ++end;
413-
tm_format.reserve(detail::to_unsigned(end - it + 1));
414-
tm_format.append(it, end);
415-
tm_format.push_back('\0');
412+
specs = {it, detail::to_unsigned(end - it)};
416413
return end;
417414
}
418415

419416
template <typename FormatContext>
420417
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) {
418+
basic_memory_buffer<Char> tm_format;
419+
tm_format.append(specs.begin(), specs.end());
420+
tm_format.push_back('\0');
421421
basic_memory_buffer<Char> buf;
422422
size_t start = buf.size();
423423
for (;;) {
@@ -440,7 +440,7 @@ template <typename Char> struct formatter<std::tm, Char> {
440440
return std::copy(buf.begin(), buf.end(), ctx.out());
441441
}
442442

443-
basic_memory_buffer<Char> tm_format;
443+
basic_string_view<Char> specs;
444444
};
445445

446446
namespace detail {
@@ -638,28 +638,29 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
638638
struct chrono_format_checker {
639639
FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); }
640640

641-
template <typename Char> void on_text(const Char*, const Char*) {}
641+
template <typename Char>
642+
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
642643
FMT_NORETURN void on_abbr_weekday() { report_no_date(); }
643644
FMT_NORETURN void on_full_weekday() { report_no_date(); }
644645
FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); }
645646
FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); }
646647
FMT_NORETURN void on_abbr_month() { report_no_date(); }
647648
FMT_NORETURN void on_full_month() { report_no_date(); }
648-
void on_24_hour(numeric_system) {}
649-
void on_12_hour(numeric_system) {}
650-
void on_minute(numeric_system) {}
651-
void on_second(numeric_system) {}
649+
FMT_CONSTEXPR void on_24_hour(numeric_system) {}
650+
FMT_CONSTEXPR void on_12_hour(numeric_system) {}
651+
FMT_CONSTEXPR void on_minute(numeric_system) {}
652+
FMT_CONSTEXPR void on_second(numeric_system) {}
652653
FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); }
653654
FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); }
654655
FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); }
655656
FMT_NORETURN void on_us_date() { report_no_date(); }
656657
FMT_NORETURN void on_iso_date() { report_no_date(); }
657-
void on_12_hour_time() {}
658-
void on_24_hour_time() {}
659-
void on_iso_time() {}
660-
void on_am_pm() {}
661-
void on_duration_value() {}
662-
void on_duration_unit() {}
658+
FMT_CONSTEXPR void on_12_hour_time() {}
659+
FMT_CONSTEXPR void on_24_hour_time() {}
660+
FMT_CONSTEXPR void on_iso_time() {}
661+
FMT_CONSTEXPR void on_am_pm() {}
662+
FMT_CONSTEXPR void on_duration_value() {}
663+
FMT_CONSTEXPR void on_duration_unit() {}
663664
FMT_NORETURN void on_utc_offset() { report_no_date(); }
664665
FMT_NORETURN void on_tz_name() { report_no_date(); }
665666
};
@@ -1033,11 +1034,11 @@ template <typename Rep, typename Period, typename Char>
10331034
struct formatter<std::chrono::duration<Rep, Period>, Char> {
10341035
private:
10351036
basic_format_specs<Char> specs;
1036-
int precision;
1037+
int precision = -1;
10371038
using arg_ref_type = detail::arg_ref<Char>;
10381039
arg_ref_type width_ref;
10391040
arg_ref_type precision_ref;
1040-
mutable basic_string_view<Char> format_str;
1041+
basic_string_view<Char> format_str;
10411042
using duration = std::chrono::duration<Rep, Period>;
10421043

10431044
struct spec_handler {
@@ -1060,17 +1061,21 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
10601061
}
10611062

10621063
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
1063-
void on_fill(basic_string_view<Char> fill) { f.specs.fill = fill; }
1064-
void on_align(align_t align) { f.specs.align = align; }
1065-
void on_width(int width) { f.specs.width = width; }
1066-
void on_precision(int _precision) { f.precision = _precision; }
1067-
void end_precision() {}
1064+
FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
1065+
f.specs.fill = fill;
1066+
}
1067+
FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; }
1068+
FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; }
1069+
FMT_CONSTEXPR void on_precision(int _precision) {
1070+
f.precision = _precision;
1071+
}
1072+
FMT_CONSTEXPR void end_precision() {}
10681073

1069-
template <typename Id> void on_dynamic_width(Id arg_id) {
1074+
template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
10701075
f.width_ref = make_arg_ref(arg_id);
10711076
}
10721077

1073-
template <typename Id> void on_dynamic_precision(Id arg_id) {
1078+
template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
10741079
f.precision_ref = make_arg_ref(arg_id);
10751080
}
10761081
};
@@ -1100,8 +1105,6 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
11001105
}
11011106

11021107
public:
1103-
formatter() : precision(-1) {}
1104-
11051108
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
11061109
-> decltype(ctx.begin()) {
11071110
auto range = do_parse(ctx);

include/fmt/core.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@
249249
# pragma execution_character_set("utf-8")
250250
#endif
251251

252+
#ifndef FMT_COMPILE_TIME_CHECKS
253+
# define FMT_COMPILE_TIME_CHECKS 0
254+
#endif
255+
252256
FMT_BEGIN_NAMESPACE
253257

254258
// Implementations of enable_if_t and other metafunctions for older systems.
@@ -1864,7 +1868,9 @@ FMT_INLINE std::basic_string<Char> vformat(
18641868
*/
18651869
// Pass char_t as a default template parameter instead of using
18661870
// std::basic_string<char_t<S>> to reduce the symbol size.
1867-
template <typename S, typename... Args, typename Char = char_t<S>>
1871+
template <typename S, typename... Args, typename Char = char_t<S>,
1872+
FMT_ENABLE_IF(!FMT_COMPILE_TIME_CHECKS ||
1873+
!std::is_same<Char, char>::value)>
18681874
FMT_INLINE std::basic_string<Char> format(const S& format_str, Args&&... args) {
18691875
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
18701876
return detail::vformat(to_string_view(format_str), vargs);

include/fmt/format-inl.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -2652,7 +2652,8 @@ struct stringifier {
26522652
} // namespace detail
26532653

26542654
template <> struct formatter<detail::bigint> {
2655-
format_parse_context::iterator parse(format_parse_context& ctx) {
2655+
FMT_CONSTEXPR format_parse_context::iterator parse(
2656+
format_parse_context& ctx) {
26562657
return ctx.begin();
26572658
}
26582659

include/fmt/format.h

+26-2
Original file line numberDiff line numberDiff line change
@@ -3065,7 +3065,7 @@ FMT_CONSTEXPR const typename ParseContext::char_type* parse_format_specs(
30653065
using mapped_type =
30663066
conditional_t<detail::mapped_type_constant<T, context>::value !=
30673067
type::custom_type,
3068-
decltype(arg_mapper<context>().map(std::declval<T>())), T>;
3068+
decltype(arg_mapper<context>().map(std::declval<const T&>())), T>;
30693069
auto f = conditional_t<has_formatter<mapped_type, context>::value,
30703070
formatter<mapped_type, char_type>,
30713071
detail::fallback_formatter<T, char_type>>();
@@ -3561,7 +3561,7 @@ template <typename Char = char> class dynamic_formatter {
35613561

35623562
public:
35633563
template <typename ParseContext>
3564-
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
3564+
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
35653565
format_str_ = ctx.begin();
35663566
// Checks are deferred to formatting time when the argument type is known.
35673567
detail::dynamic_specs_handler<ParseContext> handler(specs_, ctx);
@@ -3864,6 +3864,30 @@ make_format_to_n_args(const Args&... args) {
38643864
return format_arg_store<buffer_context<Char>, Args...>(args...);
38653865
}
38663866

3867+
#if FMT_COMPILE_TIME_CHECKS
3868+
template <typename... Args> struct format_string {
3869+
string_view str;
3870+
3871+
template <size_t N> consteval format_string(const char (&s)[N]) : str(s) {
3872+
if constexpr (detail::count_named_args<Args...>() == 0) {
3873+
using checker = detail::format_string_checker<char, detail::error_handler,
3874+
remove_cvref_t<Args>...>;
3875+
detail::parse_format_string<true>(string_view(s, N), checker(s, {}));
3876+
}
3877+
}
3878+
3879+
template <typename T,
3880+
FMT_ENABLE_IF(std::is_constructible_v<string_view, const T&>)>
3881+
format_string(const T& s) : str(s) {}
3882+
};
3883+
3884+
template <typename... Args>
3885+
FMT_INLINE std::string format(
3886+
format_string<std::type_identity_t<Args>...> format_str, Args&&... args) {
3887+
return detail::vformat(format_str.str, make_format_args(args...));
3888+
}
3889+
#endif
3890+
38673891
template <typename Char, enable_if_t<(!std::is_same<Char, char>::value), int>>
38683892
std::basic_string<Char> detail::vformat(
38693893
basic_string_view<Char> format_str,

include/fmt/ranges.h

+6-8
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,18 @@ struct formatting_range : formatting_base<Char> {
3636
static FMT_CONSTEXPR_DECL const size_t range_length_limit =
3737
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
3838
// range.
39-
Char prefix;
40-
Char delimiter;
41-
Char postfix;
42-
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
39+
Char prefix = '{';
40+
Char delimiter = ',';
41+
Char postfix = '}';
4342
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
4443
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
4544
};
4645

4746
template <typename Char, typename Enable = void>
4847
struct formatting_tuple : formatting_base<Char> {
49-
Char prefix;
50-
Char delimiter;
51-
Char postfix;
52-
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
48+
Char prefix = '(';
49+
Char delimiter = ',';
50+
Char postfix = ')';
5351
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
5452
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
5553
};

test/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ endif ()
148148

149149
message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}")
150150

151-
if (FMT_PEDANTIC)
151+
if (FMT_PEDANTIC AND CXX_STANDARD LESS 20)
152152
# MSVC fails to compile GMock when C++17 is enabled.
153153
if (FMT_HAS_VARIANT AND NOT MSVC)
154154
add_fmt_test(std-format-test)

test/chrono-test.cc

+26-24
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,14 @@ TEST(TimeTest, GMTime) {
9696
}
9797

9898
TEST(TimeTest, TimePoint) {
99-
std::chrono::system_clock::time_point point = std::chrono::system_clock::now();
99+
std::chrono::system_clock::time_point point =
100+
std::chrono::system_clock::now();
100101

101102
std::time_t t = std::chrono::system_clock::to_time_t(point);
102103
std::tm tm = *std::localtime(&t);
103104
char strftime_output[256];
104-
std::strftime(strftime_output, sizeof(strftime_output), "It is %Y-%m-%d %H:%M:%S", &tm);
105+
std::strftime(strftime_output, sizeof(strftime_output),
106+
"It is %Y-%m-%d %H:%M:%S", &tm);
105107

106108
EXPECT_EQ(strftime_output, fmt::format("It is {:%Y-%m-%d %H:%M:%S}", point));
107109
}
@@ -246,25 +248,25 @@ TEST(ChronoTest, FormatSpecs) {
246248

247249
TEST(ChronoTest, InvalidSpecs) {
248250
auto sec = std::chrono::seconds(0);
249-
EXPECT_THROW_MSG(fmt::format("{:%a}", sec), fmt::format_error, "no date");
250-
EXPECT_THROW_MSG(fmt::format("{:%A}", sec), fmt::format_error, "no date");
251-
EXPECT_THROW_MSG(fmt::format("{:%c}", sec), fmt::format_error, "no date");
252-
EXPECT_THROW_MSG(fmt::format("{:%x}", sec), fmt::format_error, "no date");
253-
EXPECT_THROW_MSG(fmt::format("{:%Ex}", sec), fmt::format_error, "no date");
254-
EXPECT_THROW_MSG(fmt::format("{:%X}", sec), fmt::format_error, "no date");
255-
EXPECT_THROW_MSG(fmt::format("{:%EX}", sec), fmt::format_error, "no date");
256-
EXPECT_THROW_MSG(fmt::format("{:%D}", sec), fmt::format_error, "no date");
257-
EXPECT_THROW_MSG(fmt::format("{:%F}", sec), fmt::format_error, "no date");
258-
EXPECT_THROW_MSG(fmt::format("{:%Ec}", sec), fmt::format_error, "no date");
259-
EXPECT_THROW_MSG(fmt::format("{:%w}", sec), fmt::format_error, "no date");
260-
EXPECT_THROW_MSG(fmt::format("{:%u}", sec), fmt::format_error, "no date");
261-
EXPECT_THROW_MSG(fmt::format("{:%b}", sec), fmt::format_error, "no date");
262-
EXPECT_THROW_MSG(fmt::format("{:%B}", sec), fmt::format_error, "no date");
263-
EXPECT_THROW_MSG(fmt::format("{:%z}", sec), fmt::format_error, "no date");
264-
EXPECT_THROW_MSG(fmt::format("{:%Z}", sec), fmt::format_error, "no date");
265-
EXPECT_THROW_MSG(fmt::format("{:%Eq}", sec), fmt::format_error,
251+
EXPECT_THROW_MSG(fmt::format(+"{:%a}", sec), fmt::format_error, "no date");
252+
EXPECT_THROW_MSG(fmt::format(+"{:%A}", sec), fmt::format_error, "no date");
253+
EXPECT_THROW_MSG(fmt::format(+"{:%c}", sec), fmt::format_error, "no date");
254+
EXPECT_THROW_MSG(fmt::format(+"{:%x}", sec), fmt::format_error, "no date");
255+
EXPECT_THROW_MSG(fmt::format(+"{:%Ex}", sec), fmt::format_error, "no date");
256+
EXPECT_THROW_MSG(fmt::format(+"{:%X}", sec), fmt::format_error, "no date");
257+
EXPECT_THROW_MSG(fmt::format(+"{:%EX}", sec), fmt::format_error, "no date");
258+
EXPECT_THROW_MSG(fmt::format(+"{:%D}", sec), fmt::format_error, "no date");
259+
EXPECT_THROW_MSG(fmt::format(+"{:%F}", sec), fmt::format_error, "no date");
260+
EXPECT_THROW_MSG(fmt::format(+"{:%Ec}", sec), fmt::format_error, "no date");
261+
EXPECT_THROW_MSG(fmt::format(+"{:%w}", sec), fmt::format_error, "no date");
262+
EXPECT_THROW_MSG(fmt::format(+"{:%u}", sec), fmt::format_error, "no date");
263+
EXPECT_THROW_MSG(fmt::format(+"{:%b}", sec), fmt::format_error, "no date");
264+
EXPECT_THROW_MSG(fmt::format(+"{:%B}", sec), fmt::format_error, "no date");
265+
EXPECT_THROW_MSG(fmt::format(+"{:%z}", sec), fmt::format_error, "no date");
266+
EXPECT_THROW_MSG(fmt::format(+"{:%Z}", sec), fmt::format_error, "no date");
267+
EXPECT_THROW_MSG(fmt::format(+"{:%Eq}", sec), fmt::format_error,
266268
"invalid format");
267-
EXPECT_THROW_MSG(fmt::format("{:%Oq}", sec), fmt::format_error,
269+
EXPECT_THROW_MSG(fmt::format(+"{:%Oq}", sec), fmt::format_error,
268270
"invalid format");
269271
}
270272

@@ -307,7 +309,7 @@ TEST(ChronoTest, FormatDefaultFP) {
307309
}
308310

309311
TEST(ChronoTest, FormatPrecision) {
310-
EXPECT_THROW_MSG(fmt::format("{:.2}", std::chrono::seconds(42)),
312+
EXPECT_THROW_MSG(fmt::format(+"{:.2}", std::chrono::seconds(42)),
311313
fmt::format_error,
312314
"precision not allowed for this argument type");
313315
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
@@ -334,7 +336,7 @@ TEST(ChronoTest, FormatSimpleQq) {
334336
}
335337

336338
TEST(ChronoTest, FormatPrecisionQq) {
337-
EXPECT_THROW_MSG(fmt::format("{:.2%Q %q}", std::chrono::seconds(42)),
339+
EXPECT_THROW_MSG(fmt::format(+"{:.2%Q %q}", std::chrono::seconds(42)),
338340
fmt::format_error,
339341
"precision not allowed for this argument type");
340342
EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234)));
@@ -351,12 +353,12 @@ TEST(ChronoTest, FormatFullSpecsQq) {
351353
}
352354

353355
TEST(ChronoTest, InvalidWidthId) {
354-
EXPECT_THROW(fmt::format("{:{o}", std::chrono::seconds(0)),
356+
EXPECT_THROW(fmt::format(+"{:{o}", std::chrono::seconds(0)),
355357
fmt::format_error);
356358
}
357359

358360
TEST(ChronoTest, InvalidColons) {
359-
EXPECT_THROW(fmt::format("{0}=:{0::", std::chrono::seconds(0)),
361+
EXPECT_THROW(fmt::format(+"{0}=:{0::", std::chrono::seconds(0)),
360362
fmt::format_error);
361363
}
362364

test/core-test.cc

+4-9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
#endif
2525

2626
#include "fmt/args.h"
27+
#if defined(FMT_COMPILE_TIME_CHECKS) && FMT_COMPILE_TIME_CHECKS
28+
# include "fmt/format.h"
29+
#endif
2730

2831
#undef min
2932
#undef max
@@ -633,7 +636,7 @@ template <> struct formatter<convertible_to_int> {
633636
};
634637

635638
template <> struct formatter<convertible_to_c_string> {
636-
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
639+
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
637640
return ctx.begin();
638641
}
639642
auto format(convertible_to_c_string, format_context& ctx)
@@ -696,18 +699,10 @@ TYPED_TEST(IsStringTest, IsString) {
696699
}
697700

698701
TEST(CoreTest, Format) {
699-
// This should work without including fmt/format.h.
700-
#ifdef FMT_FORMAT_H_
701-
# error fmt/format.h must not be included in the core test
702-
#endif
703702
EXPECT_EQ(fmt::format("{}", 42), "42");
704703
}
705704

706705
TEST(CoreTest, FormatTo) {
707-
// This should work without including fmt/format.h.
708-
#ifdef FMT_FORMAT_H_
709-
# error fmt/format.h must not be included in the core test
710-
#endif
711706
std::string s;
712707
fmt::format_to(std::back_inserter(s), "{}", 42);
713708
EXPECT_EQ(s, "42");

0 commit comments

Comments
 (0)