Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions stl/inc/format
Original file line number Diff line number Diff line change
Expand Up @@ -3537,14 +3537,14 @@ struct _Default_arg_formatter {
_OutputIt _Out;
basic_format_args<_Context> _Args;
_Lazy_locale _Loc;
basic_format_parse_context<_CharT>& _Parse_ctx;

template <class _Ty>
_OutputIt operator()(_Ty _Val) && {
return _Fmt_write<_CharT>(_STD move(_Out), _Val);
}

_OutputIt operator()(basic_format_arg<_Context>::handle _Handle) && {
basic_format_parse_context<_CharT> _Parse_ctx({});
auto _Format_ctx = _Context::_Make_from(_STD move(_Out), _Args, _Loc);
_Handle.format(_Parse_ctx, _Format_ctx);
return _Format_ctx.out();
Expand Down Expand Up @@ -3606,9 +3606,9 @@ struct _Format_checker {
: _Parse_context(_Fmt, _Num_args, _Arg_type),
_Parse_funcs{&_Compile_time_parse_format_specs<_Args, _ParseContext>...} {}
constexpr void _On_text(const _CharT*, const _CharT*) const noexcept {}
constexpr void _On_replacement_field(const size_t _Id, const _CharT*) const {
_ParseContext _Parse_ctx({});
(void) _Parse_funcs[_Id](_Parse_ctx);
constexpr void _On_replacement_field(const size_t _Id, const _CharT* _Last) {
_Parse_context.advance_to(_Parse_context.begin() + (_Last - _Parse_context.begin()._Unwrapped()));
(void) _Parse_funcs[_Id](_Parse_context);
}
constexpr const _CharT* _On_format_specs(const size_t _Id, const _CharT* _First, const _CharT*) {
_Parse_context.advance_to(_Parse_context.begin() + (_First - _Parse_context.begin()._Unwrapped()));
Expand Down Expand Up @@ -3641,14 +3641,18 @@ struct _Format_handler {
_Ctx.advance_to(_RANGES _Copy_unchecked(_First, _Last, _Ctx.out()).out);
}

void _On_replacement_field(const size_t _Id, const _CharT*) {
void _On_replacement_field(const size_t _Id, const _CharT* _Last) {
auto _Arg = _Get_arg(_Ctx, _Id);
_Ctx.advance_to(_STD visit_format_arg(
_Default_arg_formatter<_OutputIt, _CharT>{_Ctx.out(), _Ctx._Get_args(), _Ctx._Get_lazy_locale()}, _Arg));
if (_Arg._Active_state == _Basic_format_arg_type::_Custom_type) {
_Parse_context.advance_to(_Parse_context.begin() + (_Last - _Parse_context.begin()._Unwrapped()));
}
_Ctx.advance_to(_STD visit_format_arg(_Default_arg_formatter<_OutputIt, _CharT>{_Ctx.out(), _Ctx._Get_args(),
_Ctx._Get_lazy_locale(), _Parse_context},
_Arg));
}

const _CharT* _On_format_specs(const size_t _Id, const _CharT* _First, const _CharT* _Last) {
_Parse_context.advance_to(_Parse_context.begin() + (_First - &*_Parse_context.begin()));
_Parse_context.advance_to(_Parse_context.begin() + (_First - _Parse_context.begin()._Unwrapped()));
auto _Arg = _Get_arg(_Ctx, _Id);
if (_Arg._Active_state == _Basic_format_arg_type::_Custom_type) {
_Arg._Custom_state.format(_Parse_context, _Ctx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <format>
#include <iterator>
#include <limits>
Expand Down Expand Up @@ -58,10 +59,10 @@ struct not_const_formattable_type {
template <>
struct std::formatter<basic_custom_formattable_type, char> {
constexpr basic_format_parse_context<char>::iterator parse(basic_format_parse_context<char>& parse_ctx) {
if (parse_ctx.begin() != parse_ctx.end()) {
if (parse_ctx.begin() != parse_ctx.end() && *parse_ctx.begin() != '}') {
Comment thread
StephanTLavavej marked this conversation as resolved.
throw format_error{"only empty specs please"};
}
return parse_ctx.end();
return parse_ctx.begin();
Comment on lines 64 to +65

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be non-functional, correct? It's a matter of if we "match" the closing '}' or not.

}
format_context::iterator format(const basic_custom_formattable_type& val, format_context& ctx) const {
ctx.advance_to(copy(val.string_content.begin(), val.string_content.end(), ctx.out()));
Expand All @@ -72,10 +73,10 @@ struct std::formatter<basic_custom_formattable_type, char> {
template <>
struct std::formatter<not_const_formattable_type, char> {
constexpr basic_format_parse_context<char>::iterator parse(basic_format_parse_context<char>& parse_ctx) {
if (parse_ctx.begin() != parse_ctx.end()) {
if (parse_ctx.begin() != parse_ctx.end() && *parse_ctx.begin() != '}') {
throw format_error{"only empty specs please"};
}
return parse_ctx.end();
return parse_ctx.begin();
}
format_context::iterator format(not_const_formattable_type& val, format_context& ctx) const {
ctx.advance_to(copy(val.string_content.begin(), val.string_content.end(), ctx.out()));
Expand Down Expand Up @@ -276,6 +277,69 @@ void test_basic_format_context_construction() {
static_assert(!is_constructible_with_trailing_empty_brace_impl<context, OutIt, const basic_format_args<context>&>);
}

// Test GH-4636 "<format>: Call to next_arg_id may result in unexpected error (regression)"

struct FormatNextArg {};

template <class CharT>
struct std::formatter<FormatNextArg, CharT> {
public:
template <class ParseContext>
constexpr auto parse(ParseContext& ctx) {
auto it = ctx.begin();
if (it != ctx.end() && *it != '}') {
throw format_error{"Expected empty spec"};
}

arg_id = ctx.next_arg_id();
return it;
}

template <class FormatContext>
auto format(FormatNextArg, FormatContext& ctx) const {
return format_to(ctx.out(), TYPED_LITERAL(CharT, "arg-id: {}"), arg_id);
}

private:
size_t arg_id;
Comment thread
StephanTLavavej marked this conversation as resolved.
};

template <class CharT>
void test_parsing_with_next_id() {
assert(format(TYPED_LITERAL(CharT, "{}, {}"), FormatNextArg{}, 0, FormatNextArg{}, TYPED_LITERAL(CharT, "1"))
== TYPED_LITERAL(CharT, "arg-id: 1, arg-id: 3"));
assert(format(TYPED_LITERAL(CharT, "{:}, {:}"), FormatNextArg{}, 2, FormatNextArg{}, TYPED_LITERAL(CharT, "3"))
== TYPED_LITERAL(CharT, "arg-id: 1, arg-id: 3"));
}

struct NeedMagicWord {};

template <class CharT>
struct std::formatter<NeedMagicWord, CharT> {
constexpr auto parse(const basic_format_parse_context<CharT>& ctx) {
constexpr basic_string_view<CharT> magic_word{TYPED_LITERAL(CharT, "narf")};
auto [i, j] = ranges::mismatch(ctx, magic_word);
if (j != magic_word.end()) {
throw format_error{"you didn't say the magic word!"};
}

if (i != ctx.end() && *i != '}') {
throw format_error{"the whole spec must be the magic word!"};
}
return i;
}

template <class OutIt>
auto format(NeedMagicWord, basic_format_context<OutIt, CharT>& ctx) const {
return ctx.out();
}
};

template <class CharT>
void test_parsing_needing_magic_word() {
assert(format(TYPED_LITERAL(CharT, "{:narf}"), NeedMagicWord{}).empty());
}

int main() {
test_format_family_overloads<basic_custom_formattable_type>();
test_format_family_overloads<not_const_formattable_type>();
Expand All @@ -297,5 +361,9 @@ int main() {
test_basic_format_context_construction<wchar_t*, wchar_t>();
test_basic_format_context_construction<wstring::iterator, wchar_t>();
test_basic_format_context_construction<back_insert_iterator<wstring>, wchar_t>();
return 0;

test_parsing_with_next_id<char>();
test_parsing_with_next_id<wchar_t>();
test_parsing_needing_magic_word<char>();
test_parsing_needing_magic_word<wchar_t>();
}