Skip to content

Commit f3ad65c

Browse files
committed
During format string compilation, static assert that either the format string contains dynamic names, or an argument id is found for every named argument
1 parent 41634ec commit f3ad65c

File tree

1 file changed

+35
-18
lines changed

1 file changed

+35
-18
lines changed

include/fmt/compile.h

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,15 @@ constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
267267
return pos;
268268
}
269269

270-
template <typename Args, size_t POS, int ID, typename S>
270+
template <typename Args, size_t POS, int ID, bool DYNAMIC_NAMES, typename S>
271271
constexpr auto compile_format_string(S fmt);
272272

273-
template <typename Args, size_t POS, int ID, typename T, typename S>
273+
template <typename Args, size_t POS, int ID,
274+
bool DYNAMIC_NAMES, typename T, typename S>
274275
constexpr auto parse_tail(T head, S fmt) {
275276
if constexpr (POS != basic_string_view<typename S::char_type>(fmt).size()) {
276-
constexpr auto tail = compile_format_string<Args, POS, ID>(fmt);
277+
constexpr auto tail = compile_format_string<Args, POS,
278+
ID, DYNAMIC_NAMES>(fmt);
277279
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
278280
unknown_format>())
279281
return tail;
@@ -346,14 +348,14 @@ struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
346348
using type = remove_cvref_t<decltype(T::value)>;
347349
};
348350

349-
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
350-
typename S>
351+
template <typename T, typename Args, size_t END_POS, int ARG_INDEX,
352+
int NEXT_ID, bool DYNAMIC_NAMES, typename S>
351353
constexpr auto parse_replacement_field_then_tail(S fmt) {
352354
using char_type = typename S::char_type;
353355
constexpr auto str = basic_string_view<char_type>(fmt);
354356
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
355357
if constexpr (c == '}') {
356-
return parse_tail<Args, END_POS + 1, NEXT_ID>(
358+
return parse_tail<Args, END_POS + 1, NEXT_ID, DYNAMIC_NAMES>(
357359
field<char_type, typename field_type<T>::type, ARG_INDEX>(), fmt);
358360
} else if constexpr (c != ':') {
359361
FMT_THROW(format_error("expected ':'"));
@@ -364,7 +366,8 @@ constexpr auto parse_replacement_field_then_tail(S fmt) {
364366
FMT_THROW(format_error("expected '}'"));
365367
return 0;
366368
} else {
367-
return parse_tail<Args, result.end + 1, result.next_arg_id>(
369+
return parse_tail<Args, result.end + 1,
370+
result.next_arg_id, DYNAMIC_NAMES>(
368371
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
369372
result.fmt},
370373
fmt);
@@ -374,22 +377,24 @@ constexpr auto parse_replacement_field_then_tail(S fmt) {
374377

375378
// Compiles a non-empty format string and returns the compiled representation
376379
// or unknown_format() on unrecognized input.
377-
template <typename Args, size_t POS, int ID, typename S>
380+
template <typename Args, size_t POS, int ID, bool DYNAMIC_NAMES, typename S>
378381
constexpr auto compile_format_string(S fmt) {
379382
using char_type = typename S::char_type;
380383
constexpr auto str = basic_string_view<char_type>(fmt);
381384
if constexpr (str[POS] == '{') {
382385
if constexpr (POS + 1 == str.size())
383386
FMT_THROW(format_error("unmatched '{' in format string"));
384387
if constexpr (str[POS + 1] == '{') {
385-
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
388+
return parse_tail<Args, POS + 2,
389+
ID, DYNAMIC_NAMES>(make_text(str, POS, 1), fmt);
386390
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
387391
static_assert(ID != manual_indexing_id,
388392
"cannot switch from manual to automatic argument indexing");
389393
constexpr auto next_id =
390394
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
391395
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
392-
POS + 1, ID, next_id>(fmt);
396+
POS + 1, ID, next_id,
397+
DYNAMIC_NAMES>(fmt);
393398
} else {
394399
constexpr auto arg_id_result =
395400
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
@@ -404,19 +409,25 @@ constexpr auto compile_format_string(S fmt) {
404409
constexpr auto arg_index = arg_id_result.arg_id.index;
405410
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
406411
Args, arg_id_end_pos,
407-
arg_index, manual_indexing_id>(
412+
arg_index, manual_indexing_id,
413+
DYNAMIC_NAMES>(
408414
fmt);
409415
} else if constexpr (arg_id_result.kind == arg_id_kind::name) {
410416
constexpr auto arg_index =
411417
get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
418+
419+
static_assert(
420+
arg_index >= 0 || DYNAMIC_NAMES,
421+
"named argument not found");
422+
412423
if constexpr (arg_index >= 0) {
413424
constexpr auto next_id =
414425
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
415426
return parse_replacement_field_then_tail<
416427
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
417-
arg_index, next_id>(fmt);
428+
arg_index, next_id, DYNAMIC_NAMES>(fmt);
418429
} else if constexpr (c == '}') {
419-
return parse_tail<Args, arg_id_end_pos + 1, ID>(
430+
return parse_tail<Args, arg_id_end_pos + 1, ID, DYNAMIC_NAMES>(
420431
runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
421432
} else if constexpr (c == ':') {
422433
return unknown_format(); // no type info for specs parsing
@@ -426,13 +437,16 @@ constexpr auto compile_format_string(S fmt) {
426437
} else if constexpr (str[POS] == '}') {
427438
if constexpr (POS + 1 == str.size())
428439
FMT_THROW(format_error("unmatched '}' in format string"));
429-
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
440+
return parse_tail<Args, POS + 2,
441+
ID, DYNAMIC_NAMES>(make_text(str, POS, 1), fmt);
430442
} else {
431443
constexpr auto end = parse_text(str, POS + 1);
432444
if constexpr (end - POS > 1) {
433-
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), fmt);
445+
return parse_tail<Args, end,
446+
ID, DYNAMIC_NAMES>(make_text(str, POS, end - POS), fmt);
434447
} else {
435-
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, fmt);
448+
return parse_tail<Args, end,
449+
ID, DYNAMIC_NAMES>(code_unit<char_type>{str[POS]}, fmt);
436450
}
437451
}
438452
}
@@ -444,8 +458,11 @@ constexpr auto compile(S fmt) {
444458
if constexpr (str.size() == 0) {
445459
return detail::make_text(str, 0, 0);
446460
} else {
461+
constexpr int num_static_named_args =
462+
detail::count_static_named_args<Args...>();
447463
constexpr auto result =
448-
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(fmt);
464+
detail::compile_format_string<detail::type_list<Args...>, 0, 0,
465+
num_static_named_args != detail::count_named_args<Args...>()>(fmt);
449466
return result;
450467
}
451468
}
@@ -585,4 +602,4 @@ template <size_t N> class static_format_result {
585602
FMT_END_EXPORT
586603
FMT_END_NAMESPACE
587604

588-
#endif // FMT_COMPILE_H_
605+
#endif // FMT_COMPILE_H_

0 commit comments

Comments
 (0)