From e6d9c835a450ba5d26b1e66122fed9c6f42ac7e3 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Sun, 9 Jun 2024 21:17:56 +0800 Subject: [PATCH 01/11] Code movements due to dependency of tuple formatters --- stl/inc/__msvc_formatter.hpp | 45 +- stl/inc/format | 887 +++++++++++++++++------------------ 2 files changed, 465 insertions(+), 467 deletions(-) diff --git a/stl/inc/__msvc_formatter.hpp b/stl/inc/__msvc_formatter.hpp index a27138106bb..cda1abb1e65 100644 --- a/stl/inc/__msvc_formatter.hpp +++ b/stl/inc/__msvc_formatter.hpp @@ -268,17 +268,54 @@ struct formatter, _CharT> }; #if _HAS_CXX23 -_EXPORT_STD template -struct pair; +_EXPORT_STD enum class range_format { disabled, map, set, sequence, string, debug_string }; -_EXPORT_STD template -class tuple; +template +struct _Invalid_format_kind { + static_assert(_Always_false<_Ty>, "A program that instantiates the primary template of format_kind is ill-formed. " + "(N4981 [format.range.fmtkind]/1)"); +}; + +_EXPORT_STD template +constexpr _Invalid_format_kind<_Ty> format_kind; + +template +constexpr bool _Is_two_tuple = false; + +template +constexpr bool _Is_two_tuple> = true; + +template +constexpr bool _Is_two_tuple> = true; + +template <_RANGES input_range _Rng> + requires same_as<_Rng, remove_cvref_t<_Rng>> +constexpr range_format format_kind<_Rng> = []() consteval { + using _Ref_value_t = remove_cvref_t<_RANGES range_reference_t<_Rng>>; + if constexpr (same_as<_Ref_value_t, _Rng>) { + return range_format::disabled; + } else if constexpr (requires { typename _Rng::key_type; }) { + if constexpr (requires { typename _Rng::mapped_type; } && _Is_two_tuple<_Ref_value_t>) { + return range_format::map; + } else { + return range_format::set; + } + } else { + return range_format::sequence; + } +}(); // Specializations for pairs and tuples are forward-declared to avoid any risk of using the disabled primary template. // Per LWG-3997, `_CharT` in library-provided `formatter` specializations is // constrained to character types supported by `format`. +_EXPORT_STD template +struct pair; + +_EXPORT_STD template +class tuple; + template <_Format_supported_charT _CharT, class _Ty1, class _Ty2> struct formatter, _CharT>; diff --git a/stl/inc/format b/stl/inc/format index bf507722124..abe80510d91 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -2459,45 +2459,6 @@ using _Default_format_context = basic_format_context<_Basic_fmt_it<_CharT>, _Cha _EXPORT_STD using format_context = _Default_format_context; _EXPORT_STD using wformat_context = _Default_format_context; -#if _HAS_CXX23 -_EXPORT_STD enum class range_format { disabled, map, set, sequence, string, debug_string }; - -template -struct _Invalid_format_kind { - static_assert(_Always_false<_Ty>, "A program that instantiates the primary template of format_kind is ill-formed. " - "(N4964 [format.range.fmtkind]/1)"); -}; - -_EXPORT_STD template -constexpr _Invalid_format_kind<_Ty> format_kind; - -template -constexpr bool _Is_two_tuple = false; - -template -constexpr bool _Is_two_tuple> = true; - -template -constexpr bool _Is_two_tuple> = true; - -template <_RANGES input_range _Rng> - requires same_as<_Rng, remove_cvref_t<_Rng>> -constexpr range_format format_kind<_Rng> = []() consteval { - using _Ref_value_t = remove_cvref_t<_RANGES range_reference_t<_Rng>>; - if constexpr (same_as<_Ref_value_t, _Rng>) { - return range_format::disabled; - } else if constexpr (requires { typename _Rng::key_type; }) { - if constexpr (requires { typename _Rng::mapped_type; } && _Is_two_tuple<_Ref_value_t>) { - return range_format::map; - } else { - return range_format::set; - } - } else { - return range_format::sequence; - } -}(); -#endif // _HAS_CXX23 - _FMT_P2286_BEGIN template _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, monostate) { @@ -3977,8 +3938,6 @@ _NODISCARD int _Measure_display_width(const basic_string_view<_CharT> _Value) { return _Width; } -enum class _Fmt_tuple_type : uint8_t { _None, _Key_value, _No_brackets }; - template struct _Fill_align_and_width_specs { int _Width = -1; @@ -3989,12 +3948,12 @@ struct _Fill_align_and_width_specs { _CharT _Fill[4 / sizeof(_CharT)]{' '}; }; -template -class _Tuple_format_specs_setter { +template +class _Fill_align_and_width_specs_setter { public: - constexpr explicit _Tuple_format_specs_setter(_Fill_align_and_width_specs<_CharT>& _Specs_, - _Fmt_tuple_type& _Fmt_type_, basic_format_parse_context<_CharT>& _Parse_ctx_) - : _Specs(_Specs_), _Fmt_type(_Fmt_type_), _Parse_ctx(_Parse_ctx_) {} + constexpr explicit _Fill_align_and_width_specs_setter( + _Fill_align_and_width_specs<_CharT>& _Specs_, basic_format_parse_context<_CharT>& _Parse_ctx_) + : _Specs(_Specs_), _Parse_ctx(_Parse_ctx_) {} constexpr void _On_align(const _Fmt_align _Aln) { _Specs._Alignment = _Aln; @@ -4005,10 +3964,6 @@ public: _Throw_format_error("Invalid fill (too long)."); } - if (_Sv == _STATICALLY_WIDEN(_CharT, ":")) { - _Throw_format_error(R"(Invalid fill ":" for tuples and pairs.)"); - } - const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); _STD fill(_Pos, _STD end(_Specs._Fill), _CharT{}); _Specs._Fill_length = static_cast(_Sv.size()); @@ -4030,29 +3985,10 @@ public: _Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id); } - constexpr void _On_type(const _CharT _Type) { - if (_Type == _CharT{}) { - return; - } - - if (_Type == static_cast<_CharT>('n')) { - _Fmt_type = _Fmt_tuple_type::_No_brackets; - return; - } - - if constexpr (_IsTwoTuple) { - if (_Type == static_cast<_CharT>('m')) { - _Fmt_type = _Fmt_tuple_type::_Key_value; - return; - } - } - - _Throw_format_error("Invalid type specification."); - } +protected: + _Fill_align_and_width_specs<_CharT>& _Specs; private: - _Fill_align_and_width_specs<_CharT>& _Specs; - _Fmt_tuple_type& _Fmt_type; basic_format_parse_context<_CharT>& _Parse_ctx; _NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) { @@ -4064,240 +4000,261 @@ private: } }; -template -_NODISCARD constexpr const _CharT* _Parse_tuple_format_specs( - const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { - if (_Begin == _End || *_Begin == '}') { +template +struct _Range_specs : _Fill_align_and_width_specs<_CharT> { + bool _No_brackets = false; + char _Type = '\0'; +}; + +template +class _Range_specs_setter : public _Fill_align_and_width_specs_setter<_CharT> { +public: + constexpr explicit _Range_specs_setter( + _Range_specs<_CharT>& _Specs_, basic_format_parse_context<_CharT>& _Parse_ctx_) + : _Fill_align_and_width_specs_setter<_CharT>(_Specs_, _Parse_ctx_) {} + + constexpr void _On_no_brackets() { + static_cast<_Range_specs<_CharT>&>(this->_Specs)._No_brackets = true; + } + + constexpr void _On_type(const _CharT _Type) { + _STL_INTERNAL_CHECK(_Type == 'm' || _Type == 's' || _Type == '?'); + static_cast<_Range_specs<_CharT>&>(this->_Specs)._Type = static_cast(_Type); + } +}; + +template +_NODISCARD constexpr const _CharT* _Parse_range_specs( + const _CharT* _Begin, const _CharT* const _End, _Range_specs_setter<_CharT>& _Callbacks) { + if (_Begin == _End || *_Begin == '}' || *_Begin == ':') { return _Begin; } - _Begin = _STD _Parse_align(_Begin, _End, _Callbacks); + _Begin = _Parse_align(_Begin, _End, _Callbacks); if (_Begin == _End) { return _Begin; } - _Begin = _STD _Parse_width(_Begin, _End, _Callbacks); + _Begin = _Parse_width(_Begin, _End, _Callbacks); if (_Begin == _End) { return _Begin; } - // If there's anything remaining we assume it's a type. - if (*_Begin != '}') { - _Callbacks._On_type(*_Begin); + if (*_Begin == 'n') { + _Callbacks._On_no_brackets(); + if (++_Begin == _End) { + return _Begin; + } + } + + switch (const _CharT _Maybe_type = *_Begin) { + case '?': + if (++_Begin == _End || *_Begin != 's') { + _Throw_format_error("Invalid range-type '?'; was '?s' intended?"); + } + [[fallthrough]]; + case 'm': + case 's': + _Callbacks._On_type(_Maybe_type); ++_Begin; - } else { - // call the type callback so it gets a default type, this is required - // since _Specs_checker needs to be able to tell that it got a default type - // to raise an error for default formatted bools with a sign modifier - _Callbacks._On_type(_CharT{}); + break; } return _Begin; } -template -constexpr void _Set_tuple_debug_format(_FormatterType& _Formatter, _ParseContext& _Parse_ctx) { - _Formatter.parse(_Parse_ctx); - if constexpr (requires { _Formatter.set_debug_format(); }) { - _Formatter.set_debug_format(); - } -} - -template ... _Types> -class _Tuple_formatter_common_base { +_EXPORT_STD template + requires same_as, _Ty> && formattable<_Ty, _CharT> +class range_formatter { private: - tuple, _CharT>...> _Underlying; + formatter<_Ty, _CharT> _Underlying; basic_string_view<_CharT> _Separator = _STATICALLY_WIDEN(_CharT, ", "); - basic_string_view<_CharT> _Opening_bracket = _STATICALLY_WIDEN(_CharT, "("); - basic_string_view<_CharT> _Closing_bracket = _STATICALLY_WIDEN(_CharT, ")"); - - _Fill_align_and_width_specs<_CharT> _Specs; - - template - void _Format_to_context(_FormatContext& _Fmt_ctx, _ArgTypes&... _Args) const { - _STD _Copy_unchecked(_Opening_bracket._Unchecked_begin(), _Opening_bracket._Unchecked_end(), _Fmt_ctx.out()); - [&](index_sequence<_Indices...>) { - auto _Single_writer = [&](auto& _Arg) { - if constexpr (_Idx != 0) { - _STD _Copy_unchecked(_Separator._Unchecked_begin(), _Separator._Unchecked_end(), _Fmt_ctx.out()); - } - _STD get<_Idx>(_Underlying).format(_Arg, _Fmt_ctx); - }; - (_Single_writer.template operator()<_Indices>(_Args), ...); - }(index_sequence_for<_ArgTypes...>{}); - _STD _Copy_unchecked(_Closing_bracket._Unchecked_begin(), _Closing_bracket._Unchecked_end(), _Fmt_ctx.out()); - } - -protected: - static constexpr bool _Is_const_formattable = (formattable && ...); - - template - _FormatContext::iterator _Format(_FormatContext& _Fmt_ctx, _ArgTypes&... _Args) const { - _STL_INTERNAL_STATIC_ASSERT( - (is_same_v<_ArgTypes, remove_reference_t<_Maybe_const<_Is_const_formattable, _Types>>> && ...)); - - auto _Format_specs = _Specs; - if (_Specs._Dynamic_width_index >= 0) { - _Format_specs._Width = - _STD _Get_dynamic_specs<_Width_checker>(_Fmt_ctx.arg(static_cast(_Specs._Dynamic_width_index))); - } - - if (_Format_specs._Width <= 0) { - _Format_to_context(_Fmt_ctx, _Args...); - return _Fmt_ctx.out(); - } - - basic_string<_CharT> _Tmp_str; - if constexpr (is_same_v<_FormatContext, _Default_format_context<_CharT>>) { - _Fmt_iterator_buffer>, _CharT> _Tmp_buf{ - back_insert_iterator{_Tmp_str}}; - auto _Tmp_ctx = _FormatContext::_Make_from( - _Basic_fmt_it<_CharT>{_Tmp_buf}, _Fmt_ctx._Get_args(), _Fmt_ctx._Get_lazy_locale()); - _Format_to_context(_Tmp_ctx, _Args...); - } else { - _CSTD abort(); // no basic_format_context object other than _Default_format_context can be created - } - - const int _Width = _Measure_display_width<_CharT>(_Tmp_str); - return _STD _Write_aligned( - _Fmt_ctx.out(), _Width, _Format_specs, _Fmt_align::_Left, [&](typename _FormatContext::iterator _Out) { - return _STD _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Tmp_str}); - }); - } + basic_string_view<_CharT> _Opening_bracket = _STATICALLY_WIDEN(_CharT, "["); + basic_string_view<_CharT> _Closing_bracket = _STATICALLY_WIDEN(_CharT, "]"); + _Range_specs<_CharT> _Specs; public: - constexpr void set_separator(const basic_string_view<_CharT> _Sep) noexcept { + constexpr void set_separator(basic_string_view<_CharT> _Sep) noexcept { _Separator = _Sep; } - constexpr void set_brackets( - const basic_string_view<_CharT> _Opening, const basic_string_view<_CharT> _Closing) noexcept { + constexpr void set_brackets(basic_string_view<_CharT> _Opening, basic_string_view<_CharT> _Closing) noexcept { _Opening_bracket = _Opening; _Closing_bracket = _Closing; } + _NODISCARD constexpr formatter<_Ty, _CharT>& underlying() noexcept { + return _Underlying; + } + + _NODISCARD constexpr const formatter<_Ty, _CharT>& underlying() const noexcept { + return _Underlying; + } + template constexpr _ParseContext::iterator parse(_ParseContext& _Ctx) { - _Fmt_tuple_type _Fmt_type = _Fmt_tuple_type::_None; - _Tuple_format_specs_setter<_CharT, sizeof...(_Types) == 2> _Callback{_Specs, _Fmt_type, _Ctx}; - const auto _It = _STD _Parse_tuple_format_specs(_Ctx._Unchecked_begin(), _Ctx._Unchecked_end(), _Callback); - if (_It != _Ctx._Unchecked_end() && *_It != '}') { - _STD _Throw_format_error("Missing '}' in format string."); - } + _Range_specs_setter<_CharT> _Callback{_Specs, _Ctx}; + auto _It = _STD _Parse_range_specs(_Ctx._Unchecked_begin(), _Ctx._Unchecked_end(), _Callback); - if (_Fmt_type == _Fmt_tuple_type::_No_brackets) { - set_brackets({}, {}); - } else if constexpr (sizeof...(_Types) == 2) { - if (_Fmt_type == _Fmt_tuple_type::_Key_value) { - set_separator(_STATICALLY_WIDEN(_CharT, ": ")); - set_brackets({}, {}); + _Ctx.advance_to(_Ctx.begin() + (_It - _Ctx._Unchecked_begin())); + bool _Has_underlying_spec = false; + if (_It != _Ctx._Unchecked_end()) { + if (*_It == ':') { + _Has_underlying_spec = true; + _Ctx.advance_to(_Ctx.begin() + 1); + } else if (*_It != '}') { + _Throw_format_error("Invalid range-format-spec."); } } - _Ctx.advance_to(_Ctx.begin() + (_It - _Ctx._Unchecked_begin())); - _STD apply([&_Ctx](auto&... _Elems) { (_Set_tuple_debug_format(_Elems, _Ctx), ...); }, _Underlying); + _It = _Underlying.parse(_Ctx)._Unwrapped(); + if (_It != _Ctx._Unchecked_end() && *_It != '}') { + _Throw_format_error("Missing '}' in format string."); + } - return _Ctx.begin(); - } -}; + switch (_Specs._Type) { + case 'm': + if constexpr (_Is_two_tuple<_Ty>) { + set_brackets(_STATICALLY_WIDEN(_CharT, "{"), _STATICALLY_WIDEN(_CharT, "}")); + set_separator(_STATICALLY_WIDEN(_CharT, ", ")); + _Underlying.set_brackets({}, {}); + _Underlying.set_separator(_STATICALLY_WIDEN(_CharT, ": ")); + } else { + _Throw_format_error("Range-type 'm' requires type T to be a pair or a 2-element tuple."); + } + [[fallthrough]]; -// formatter definition for all pairs and tuples, the deleted default constructor -// makes it "disabled" as per N4971 [format.formatter.spec]/5 + case '\0': + if constexpr (requires { _Underlying.set_debug_format(); }) { + if (!_Has_underlying_spec) { + _Underlying.set_debug_format(); + } + } + break; -template -struct _Tuple_formatter_base { - _Tuple_formatter_base() = delete; - _Tuple_formatter_base(const _Tuple_formatter_base&) = delete; - _Tuple_formatter_base& operator=(const _Tuple_formatter_base&) = delete; -}; + case 's': + case '?': + if constexpr (same_as<_Ty, _CharT>) { + if (_Specs._No_brackets) { + _Throw_format_error("Range-types 's' and '?s' cannot be combined with the 'n' option."); + } else if (_Has_underlying_spec) { + _Throw_format_error("Range-types 's' and '?s' cannot be combined with a range-underlying-spec."); + } + } else { + _Throw_format_error("Range-types 's' and '?s' require type T to be charT."); + } -template _Ty1, formattable<_CharT> _Ty2> -struct _Tuple_formatter_base, _CharT> : _Tuple_formatter_common_base<_CharT, _Ty1, _Ty2> { -private: - using _Base = _Tuple_formatter_common_base<_CharT, _Ty1, _Ty2>; - using _Formatted_type = _Maybe_const<_Base::_Is_const_formattable, pair<_Ty1, _Ty2>>; + break; + } -public: - template - _FormatContext::iterator format(_Formatted_type& _Elems, _FormatContext& _Ctx) const { - return this->_Format(_Ctx, _Elems.first, _Elems.second); + if (_Specs._No_brackets) { + set_brackets({}, {}); + } + + return _Ctx.begin() + (_It - _Ctx._Unchecked_begin()); } -}; -template ... _Types> -struct _Tuple_formatter_base, _CharT> : _Tuple_formatter_common_base<_CharT, _Types...> { -private: - using _Base = _Tuple_formatter_common_base<_CharT, _Types...>; - using _Formatted_type = _Maybe_const<_Base::_Is_const_formattable, tuple<_Types...>>; + template <_RANGES input_range _Range, class _FormatContext> + requires formattable<_RANGES range_reference_t<_Range>, _CharT> + && same_as>, _Ty> + _FormatContext::iterator format(_Range&& _Rng, _FormatContext& _Ctx) const { + return _Format(_STD forward<_Range>(_Rng), _Ctx); + } -public: - template - _FormatContext::iterator format(_Formatted_type& _Elems, _FormatContext& _Ctx) const { - return _STD apply([this, &_Ctx](auto&... _Args) { return this->_Format(_Ctx, _Args...); }, _Elems); +private: + template <_RANGES input_range _Range, class _FormatContext> + _FormatContext::iterator _Format(_Range&&, _FormatContext&) const { + _Throw_format_error("Unsupported 'basic_format_context'."); } -}; -// Per LWG-3997, `_CharT` in library-provided `formatter` specializations is -// constrained to character types supported by `format`. + template <_RANGES input_range _Range, class _FormatContext> + requires _Is_specialization_v + && derived_from> + _FormatContext::iterator _Format(_Range&& _Rng, _FormatContext& _Ctx) const { + auto _Format_specs = _Specs; + if (_Specs._Dynamic_width_index >= 0) { + _Format_specs._Width = + _STD _Get_dynamic_specs<_Width_checker>(_Ctx.arg(static_cast(_Specs._Dynamic_width_index))); + } -template <_Format_supported_charT _CharT, class _Ty1, class _Ty2> -struct formatter, _CharT> : _Tuple_formatter_base, _CharT> {}; + basic_string<_CharT> _Buffer; + { + _Fmt_iterator_buffer>, _CharT> _Fmt_buf( + back_insert_iterator{_Buffer}); + using _Inserter = back_insert_iterator<_Fmt_buffer<_CharT>>; + auto _Nested_context = basic_format_context<_Inserter, _CharT>::_Make_from( + _Inserter{_Fmt_buf}, _Ctx._Get_args(), _Ctx._Get_lazy_locale()); -template <_Format_supported_charT _CharT, class... _Types> -struct formatter, _CharT> : _Tuple_formatter_base, _CharT> {}; + if constexpr (same_as<_Ty, _CharT>) { + if (_Specs._Type == 's' || _Specs._Type == '?') { + _Format_as_string(_STD forward<_Range>(_Rng), _Nested_context, _Specs._Type == '?'); + } else { + _Format_as_sequence(_STD forward<_Range>(_Rng), _Nested_context); + } + } else { + _Format_as_sequence(_STD forward<_Range>(_Rng), _Nested_context); + } + } -enum class _Add_newline : bool { _Nope, _Yes }; + const int _Width = _Measure_display_width<_CharT>(_Buffer); + return _STD _Write_aligned( + _Ctx.out(), _Width, _Format_specs, _Fmt_align::_Left, [&](_FormatContext::iterator _Out) { + return _STD _Fmt_write(_STD move(_Out), basic_string_view{_Buffer}); + }); + } -_NODISCARD inline string _Unescape_braces(const _Add_newline _Add_nl, const string_view _Old_str) { - string _Unescaped_str; + template <_RANGES input_range _Range, class _FormatContext> + void _Format_as_sequence(_Range&& _Rng, _FormatContext& _Ctx) const { + _Ctx.advance_to(_STD _Fmt_write(_Ctx.out(), _Opening_bracket)); + bool _Separate = false; + for (auto&& _Elem : _Rng) { + if (_Separate) { + _Ctx.advance_to(_STD _Fmt_write(_Ctx.out(), _Separator)); + } - if (_Old_str.empty()) { - if (_Add_nl == _Add_newline::_Yes) { - _Unescaped_str.push_back('\n'); + _Separate = true; + _Ctx.advance_to(_Underlying.format(_Elem, _Ctx)); } - return _Unescaped_str; + (void) _STD _Fmt_write(_Ctx.out(), _Closing_bracket); } - size_t _Unescaped_str_expected_size = _Old_str.size(); - - if (_Add_nl == _Add_newline::_Yes) { - ++_Unescaped_str_expected_size; - } + template <_RANGES input_range _Range, class _FormatContext> + void _Format_as_string(_Range&& _Rng, _FormatContext& _Ctx, const bool _Debug) const { + if constexpr (_RANGES contiguous_range<_Range>) { + const auto _Size = _STD _To_unsigned_like(_RANGES distance(_Rng)); - _Unescaped_str.resize_and_overwrite(_Unescaped_str_expected_size, [_Add_nl, _Old_str](char* _Dest_ptr, size_t) { - char _Prev_char = _Old_str.front(); - size_t _Num_chars_written = 1; - *_Dest_ptr++ = _Prev_char; + if (!_STD in_range(_Size)) [[unlikely]] { + _Throw_format_error("Formatted range is too long."); + } - for (const auto _Curr_char : _Old_str.substr(1)) { - if ((_Curr_char == '{' && _Prev_char == '{') || (_Curr_char == '}' && _Prev_char == '}')) { - _Prev_char = '\0'; - } else { - *_Dest_ptr++ = _Curr_char; - ++_Num_chars_written; + formatter, _CharT> _String_view_formatter; + if (_Debug) { + _String_view_formatter.set_debug_format(); + } - _Prev_char = _Curr_char; + const basic_string_view<_CharT> _Str(_STD to_address(_RANGES begin(_Rng)), static_cast(_Size)); + _String_view_formatter.format(_Str, _Ctx); + } else { + using _String = basic_string<_CharT>; + formatter<_String, _CharT> _String_formatter; + if (_Debug) { + _String_formatter.set_debug_format(); } - } - if (_Add_nl == _Add_newline::_Yes) { - *_Dest_ptr = '\n'; - ++_Num_chars_written; + _String_formatter.format(_String{from_range, _Rng}, _Ctx); } + } +}; - return _Num_chars_written; - }); - - return _Unescaped_str; -} +enum class _Fmt_tuple_type : uint8_t { _None, _Key_value, _No_brackets }; -template -class _Fill_align_and_width_specs_setter { +template +class _Tuple_format_specs_setter { public: - constexpr explicit _Fill_align_and_width_specs_setter( - _Fill_align_and_width_specs<_CharT>& _Specs_, basic_format_parse_context<_CharT>& _Parse_ctx_) - : _Specs(_Specs_), _Parse_ctx(_Parse_ctx_) {} + constexpr explicit _Tuple_format_specs_setter(_Fill_align_and_width_specs<_CharT>& _Specs_, + _Fmt_tuple_type& _Fmt_type_, basic_format_parse_context<_CharT>& _Parse_ctx_) + : _Specs(_Specs_), _Fmt_type(_Fmt_type_), _Parse_ctx(_Parse_ctx_) {} constexpr void _On_align(const _Fmt_align _Aln) { _Specs._Alignment = _Aln; @@ -4308,6 +4265,10 @@ public: _Throw_format_error("Invalid fill (too long)."); } + if (_Sv == _STATICALLY_WIDEN(_CharT, ":")) { + _Throw_format_error(R"(Invalid fill ":" for tuples and pairs.)"); + } + const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); _STD fill(_Pos, _STD end(_Specs._Fill), _CharT{}); _Specs._Fill_length = static_cast(_Sv.size()); @@ -4329,10 +4290,29 @@ public: _Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id); } -protected: - _Fill_align_and_width_specs<_CharT>& _Specs; + constexpr void _On_type(const _CharT _Type) { + if (_Type == _CharT{}) { + return; + } + + if (_Type == static_cast<_CharT>('n')) { + _Fmt_type = _Fmt_tuple_type::_No_brackets; + return; + } + + if constexpr (_IsTwoTuple) { + if (_Type == static_cast<_CharT>('m')) { + _Fmt_type = _Fmt_tuple_type::_Key_value; + return; + } + } + + _Throw_format_error("Invalid type specification."); + } private: + _Fill_align_and_width_specs<_CharT>& _Specs; + _Fmt_tuple_type& _Fmt_type; basic_format_parse_context<_CharT>& _Parse_ctx; _NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) { @@ -4345,295 +4325,276 @@ private: }; template -_NODISCARD constexpr const _CharT* _Parse_fill_align_and_width_specs( +_NODISCARD constexpr const _CharT* _Parse_tuple_format_specs( const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { if (_Begin == _End || *_Begin == '}') { return _Begin; } - _Begin = _Parse_align(_Begin, _End, _Callbacks); + _Begin = _STD _Parse_align(_Begin, _End, _Callbacks); if (_Begin == _End) { return _Begin; } - return _Parse_width(_Begin, _End, _Callbacks); -} - -template -struct _Fill_align_and_width_formatter { -public: - _NODISCARD constexpr auto _Parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - _Fill_align_and_width_specs_setter<_CharT> _Callback{_Specs, _Parse_ctx}; - const auto _It = - _Parse_fill_align_and_width_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); - if (_It != _Parse_ctx._Unchecked_end() && *_It != '}') { - _Throw_format_error("Missing '}' in format string."); - } + _Begin = _STD _Parse_width(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } - return _Parse_ctx.begin() + (_It - _Parse_ctx._Unchecked_begin()); + // If there's anything remaining we assume it's a type. + if (*_Begin != '}') { + _Callbacks._On_type(*_Begin); + ++_Begin; + } else { + // call the type callback so it gets a default type, this is required + // since _Specs_checker needs to be able to tell that it got a default type + // to raise an error for default formatted bools with a sign modifier + _Callbacks._On_type(_CharT{}); } - template - _NODISCARD constexpr auto _Format( - _FormatContext& _Format_ctx, const int _Width, _Fmt_align _Default_align, _Func&& _Fn) const { - _Fill_align_and_width_specs _Format_specs = _Specs; - if (_Specs._Dynamic_width_index >= 0) { - _Format_specs._Width = - _Get_dynamic_specs<_Width_checker>(_Format_ctx.arg(static_cast(_Specs._Dynamic_width_index))); - } + return _Begin; +} - return _Write_aligned(_Format_ctx.out(), _Width, _Format_specs, _Default_align, _STD forward<_Func>(_Fn)); +template +constexpr void _Set_tuple_debug_format(_FormatterType& _Formatter, _ParseContext& _Parse_ctx) { + _Formatter.parse(_Parse_ctx); + if constexpr (requires { _Formatter.set_debug_format(); }) { + _Formatter.set_debug_format(); } +} +template ... _Types> +class _Tuple_formatter_common_base { private: - _Fill_align_and_width_specs<_CharT> _Specs; -}; + tuple, _CharT>...> _Underlying; + basic_string_view<_CharT> _Separator = _STATICALLY_WIDEN(_CharT, ", "); + basic_string_view<_CharT> _Opening_bracket = _STATICALLY_WIDEN(_CharT, "("); + basic_string_view<_CharT> _Closing_bracket = _STATICALLY_WIDEN(_CharT, ")"); -template -struct _Range_specs : _Fill_align_and_width_specs<_CharT> { - bool _No_brackets = false; - char _Type = '\0'; -}; + _Fill_align_and_width_specs<_CharT> _Specs; -template -class _Range_specs_setter : public _Fill_align_and_width_specs_setter<_CharT> { -public: - constexpr explicit _Range_specs_setter( - _Range_specs<_CharT>& _Specs_, basic_format_parse_context<_CharT>& _Parse_ctx_) - : _Fill_align_and_width_specs_setter<_CharT>(_Specs_, _Parse_ctx_) {} - - constexpr void _On_no_brackets() { - static_cast<_Range_specs<_CharT>&>(this->_Specs)._No_brackets = true; - } - - constexpr void _On_type(const _CharT _Type) { - _STL_INTERNAL_CHECK(_Type == 'm' || _Type == 's' || _Type == '?'); - static_cast<_Range_specs<_CharT>&>(this->_Specs)._Type = static_cast(_Type); - } -}; - -template -_NODISCARD constexpr const _CharT* _Parse_range_specs( - const _CharT* _Begin, const _CharT* const _End, _Range_specs_setter<_CharT>& _Callbacks) { - if (_Begin == _End || *_Begin == '}' || *_Begin == ':') { - return _Begin; + template + void _Format_to_context(_FormatContext& _Fmt_ctx, _ArgTypes&... _Args) const { + _STD _Copy_unchecked(_Opening_bracket._Unchecked_begin(), _Opening_bracket._Unchecked_end(), _Fmt_ctx.out()); + [&](index_sequence<_Indices...>) { + auto _Single_writer = [&](auto& _Arg) { + if constexpr (_Idx != 0) { + _STD _Copy_unchecked(_Separator._Unchecked_begin(), _Separator._Unchecked_end(), _Fmt_ctx.out()); + } + _STD get<_Idx>(_Underlying).format(_Arg, _Fmt_ctx); + }; + (_Single_writer.template operator()<_Indices>(_Args), ...); + }(index_sequence_for<_ArgTypes...>{}); + _STD _Copy_unchecked(_Closing_bracket._Unchecked_begin(), _Closing_bracket._Unchecked_end(), _Fmt_ctx.out()); } - _Begin = _Parse_align(_Begin, _End, _Callbacks); - if (_Begin == _End) { - return _Begin; - } +protected: + static constexpr bool _Is_const_formattable = (formattable && ...); - _Begin = _Parse_width(_Begin, _End, _Callbacks); - if (_Begin == _End) { - return _Begin; - } + template + _FormatContext::iterator _Format(_FormatContext& _Fmt_ctx, _ArgTypes&... _Args) const { + _STL_INTERNAL_STATIC_ASSERT( + (is_same_v<_ArgTypes, remove_reference_t<_Maybe_const<_Is_const_formattable, _Types>>> && ...)); - if (*_Begin == 'n') { - _Callbacks._On_no_brackets(); - if (++_Begin == _End) { - return _Begin; + auto _Format_specs = _Specs; + if (_Specs._Dynamic_width_index >= 0) { + _Format_specs._Width = + _STD _Get_dynamic_specs<_Width_checker>(_Fmt_ctx.arg(static_cast(_Specs._Dynamic_width_index))); } - } - switch (const _CharT _Maybe_type = *_Begin) { - case '?': - if (++_Begin == _End || *_Begin != 's') { - _Throw_format_error("Invalid range-type '?'; was '?s' intended?"); + if (_Format_specs._Width <= 0) { + _Format_to_context(_Fmt_ctx, _Args...); + return _Fmt_ctx.out(); } - [[fallthrough]]; - case 'm': - case 's': - _Callbacks._On_type(_Maybe_type); - ++_Begin; - break; - } - return _Begin; -} + basic_string<_CharT> _Tmp_str; + if constexpr (is_same_v<_FormatContext, _Default_format_context<_CharT>>) { + _Fmt_iterator_buffer>, _CharT> _Tmp_buf{ + back_insert_iterator{_Tmp_str}}; + auto _Tmp_ctx = _FormatContext::_Make_from( + _Basic_fmt_it<_CharT>{_Tmp_buf}, _Fmt_ctx._Get_args(), _Fmt_ctx._Get_lazy_locale()); + _Format_to_context(_Tmp_ctx, _Args...); + } else { + _CSTD abort(); // no basic_format_context object other than _Default_format_context can be created + } -_EXPORT_STD template - requires same_as, _Ty> && formattable<_Ty, _CharT> -class range_formatter { -private: - formatter<_Ty, _CharT> _Underlying; - basic_string_view<_CharT> _Separator = _STATICALLY_WIDEN(_CharT, ", "); - basic_string_view<_CharT> _Opening_bracket = _STATICALLY_WIDEN(_CharT, "["); - basic_string_view<_CharT> _Closing_bracket = _STATICALLY_WIDEN(_CharT, "]"); - _Range_specs<_CharT> _Specs; + const int _Width = _Measure_display_width<_CharT>(_Tmp_str); + return _STD _Write_aligned( + _Fmt_ctx.out(), _Width, _Format_specs, _Fmt_align::_Left, [&](typename _FormatContext::iterator _Out) { + return _STD _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Tmp_str}); + }); + } public: - constexpr void set_separator(basic_string_view<_CharT> _Sep) noexcept { + constexpr void set_separator(const basic_string_view<_CharT> _Sep) noexcept { _Separator = _Sep; } - constexpr void set_brackets(basic_string_view<_CharT> _Opening, basic_string_view<_CharT> _Closing) noexcept { + constexpr void set_brackets( + const basic_string_view<_CharT> _Opening, const basic_string_view<_CharT> _Closing) noexcept { _Opening_bracket = _Opening; _Closing_bracket = _Closing; } - _NODISCARD constexpr formatter<_Ty, _CharT>& underlying() noexcept { - return _Underlying; - } - - _NODISCARD constexpr const formatter<_Ty, _CharT>& underlying() const noexcept { - return _Underlying; - } - template constexpr _ParseContext::iterator parse(_ParseContext& _Ctx) { - _Range_specs_setter<_CharT> _Callback{_Specs, _Ctx}; - auto _It = _STD _Parse_range_specs(_Ctx._Unchecked_begin(), _Ctx._Unchecked_end(), _Callback); - - _Ctx.advance_to(_Ctx.begin() + (_It - _Ctx._Unchecked_begin())); - bool _Has_underlying_spec = false; - if (_It != _Ctx._Unchecked_end()) { - if (*_It == ':') { - _Has_underlying_spec = true; - _Ctx.advance_to(_Ctx.begin() + 1); - } else if (*_It != '}') { - _Throw_format_error("Invalid range-format-spec."); - } - } - - _It = _Underlying.parse(_Ctx)._Unwrapped(); + _Fmt_tuple_type _Fmt_type = _Fmt_tuple_type::_None; + _Tuple_format_specs_setter<_CharT, sizeof...(_Types) == 2> _Callback{_Specs, _Fmt_type, _Ctx}; + const auto _It = _STD _Parse_tuple_format_specs(_Ctx._Unchecked_begin(), _Ctx._Unchecked_end(), _Callback); if (_It != _Ctx._Unchecked_end() && *_It != '}') { - _Throw_format_error("Missing '}' in format string."); + _STD _Throw_format_error("Missing '}' in format string."); } - switch (_Specs._Type) { - case 'm': - if constexpr (_Is_two_tuple<_Ty>) { - set_brackets(_STATICALLY_WIDEN(_CharT, "{"), _STATICALLY_WIDEN(_CharT, "}")); - set_separator(_STATICALLY_WIDEN(_CharT, ", ")); - _Underlying.set_brackets({}, {}); - _Underlying.set_separator(_STATICALLY_WIDEN(_CharT, ": ")); - } else { - _Throw_format_error("Range-type 'm' requires type T to be a pair or a 2-element tuple."); + if (_Fmt_type == _Fmt_tuple_type::_No_brackets) { + set_brackets({}, {}); + } else if constexpr (sizeof...(_Types) == 2) { + if (_Fmt_type == _Fmt_tuple_type::_Key_value) { + set_separator(_STATICALLY_WIDEN(_CharT, ": ")); + set_brackets({}, {}); } - [[fallthrough]]; + } - case '\0': - if constexpr (requires { _Underlying.set_debug_format(); }) { - if (!_Has_underlying_spec) { - _Underlying.set_debug_format(); - } - } - break; + _Ctx.advance_to(_Ctx.begin() + (_It - _Ctx._Unchecked_begin())); + _STD apply([&_Ctx](auto&... _Elems) { (_Set_tuple_debug_format(_Elems, _Ctx), ...); }, _Underlying); - case 's': - case '?': - if constexpr (same_as<_Ty, _CharT>) { - if (_Specs._No_brackets) { - _Throw_format_error("Range-types 's' and '?s' cannot be combined with the 'n' option."); - } else if (_Has_underlying_spec) { - _Throw_format_error("Range-types 's' and '?s' cannot be combined with a range-underlying-spec."); - } - } else { - _Throw_format_error("Range-types 's' and '?s' require type T to be charT."); - } + return _Ctx.begin(); + } +}; - break; - } +// formatter definition for all pairs and tuples, the deleted default constructor +// makes it "disabled" as per N4971 [format.formatter.spec]/5 - if (_Specs._No_brackets) { - set_brackets({}, {}); - } +template +struct _Tuple_formatter_base { + _Tuple_formatter_base() = delete; + _Tuple_formatter_base(const _Tuple_formatter_base&) = delete; + _Tuple_formatter_base& operator=(const _Tuple_formatter_base&) = delete; +}; - return _Ctx.begin() + (_It - _Ctx._Unchecked_begin()); - } +template _Ty1, formattable<_CharT> _Ty2> +struct _Tuple_formatter_base, _CharT> : _Tuple_formatter_common_base<_CharT, _Ty1, _Ty2> { +private: + using _Base = _Tuple_formatter_common_base<_CharT, _Ty1, _Ty2>; + using _Formatted_type = _Maybe_const<_Base::_Is_const_formattable, pair<_Ty1, _Ty2>>; - template <_RANGES input_range _Range, class _FormatContext> - requires formattable<_RANGES range_reference_t<_Range>, _CharT> - && same_as>, _Ty> - _FormatContext::iterator format(_Range&& _Rng, _FormatContext& _Ctx) const { - return _Format(_STD forward<_Range>(_Rng), _Ctx); +public: + template + _FormatContext::iterator format(_Formatted_type& _Elems, _FormatContext& _Ctx) const { + return this->_Format(_Ctx, _Elems.first, _Elems.second); } +}; +template ... _Types> +struct _Tuple_formatter_base, _CharT> : _Tuple_formatter_common_base<_CharT, _Types...> { private: - template <_RANGES input_range _Range, class _FormatContext> - _FormatContext::iterator _Format(_Range&&, _FormatContext&) const { - _Throw_format_error("Unsupported 'basic_format_context'."); + using _Base = _Tuple_formatter_common_base<_CharT, _Types...>; + using _Formatted_type = _Maybe_const<_Base::_Is_const_formattable, tuple<_Types...>>; + +public: + template + _FormatContext::iterator format(_Formatted_type& _Elems, _FormatContext& _Ctx) const { + return _STD apply([this, &_Ctx](auto&... _Args) { return this->_Format(_Ctx, _Args...); }, _Elems); } +}; - template <_RANGES input_range _Range, class _FormatContext> - requires _Is_specialization_v - && derived_from> - _FormatContext::iterator _Format(_Range&& _Rng, _FormatContext& _Ctx) const { - auto _Format_specs = _Specs; - if (_Specs._Dynamic_width_index >= 0) { - _Format_specs._Width = - _STD _Get_dynamic_specs<_Width_checker>(_Ctx.arg(static_cast(_Specs._Dynamic_width_index))); - } +// Per LWG-3997, `_CharT` in library-provided `formatter` specializations is +// constrained to character types supported by `format`. - basic_string<_CharT> _Buffer; - { - _Fmt_iterator_buffer>, _CharT> _Fmt_buf( - back_insert_iterator{_Buffer}); - using _Inserter = back_insert_iterator<_Fmt_buffer<_CharT>>; - auto _Nested_context = basic_format_context<_Inserter, _CharT>::_Make_from( - _Inserter{_Fmt_buf}, _Ctx._Get_args(), _Ctx._Get_lazy_locale()); +template <_Format_supported_charT _CharT, class _Ty1, class _Ty2> +struct formatter, _CharT> : _Tuple_formatter_base, _CharT> {}; - if constexpr (same_as<_Ty, _CharT>) { - if (_Specs._Type == 's' || _Specs._Type == '?') { - _Format_as_string(_STD forward<_Range>(_Rng), _Nested_context, _Specs._Type == '?'); - } else { - _Format_as_sequence(_STD forward<_Range>(_Rng), _Nested_context); - } - } else { - _Format_as_sequence(_STD forward<_Range>(_Rng), _Nested_context); - } +template <_Format_supported_charT _CharT, class... _Types> +struct formatter, _CharT> : _Tuple_formatter_base, _CharT> {}; + +enum class _Add_newline : bool { _Nope, _Yes }; + +_NODISCARD inline string _Unescape_braces(const _Add_newline _Add_nl, const string_view _Old_str) { + string _Unescaped_str; + + if (_Old_str.empty()) { + if (_Add_nl == _Add_newline::_Yes) { + _Unescaped_str.push_back('\n'); } - const int _Width = _Measure_display_width<_CharT>(_Buffer); - return _STD _Write_aligned( - _Ctx.out(), _Width, _Format_specs, _Fmt_align::_Left, [&](_FormatContext::iterator _Out) { - return _STD _Fmt_write(_STD move(_Out), basic_string_view{_Buffer}); - }); + return _Unescaped_str; } - template <_RANGES input_range _Range, class _FormatContext> - void _Format_as_sequence(_Range&& _Rng, _FormatContext& _Ctx) const { - _Ctx.advance_to(_STD _Fmt_write(_Ctx.out(), _Opening_bracket)); - bool _Separate = false; - for (auto&& _Elem : _Rng) { - if (_Separate) { - _Ctx.advance_to(_STD _Fmt_write(_Ctx.out(), _Separator)); + size_t _Unescaped_str_expected_size = _Old_str.size(); + + if (_Add_nl == _Add_newline::_Yes) { + ++_Unescaped_str_expected_size; + } + + _Unescaped_str.resize_and_overwrite(_Unescaped_str_expected_size, [_Add_nl, _Old_str](char* _Dest_ptr, size_t) { + char _Prev_char = _Old_str.front(); + size_t _Num_chars_written = 1; + *_Dest_ptr++ = _Prev_char; + + for (const auto _Curr_char : _Old_str.substr(1)) { + if ((_Curr_char == '{' && _Prev_char == '{') || (_Curr_char == '}' && _Prev_char == '}')) { + _Prev_char = '\0'; + } else { + *_Dest_ptr++ = _Curr_char; + ++_Num_chars_written; + + _Prev_char = _Curr_char; } + } - _Separate = true; - _Ctx.advance_to(_Underlying.format(_Elem, _Ctx)); + if (_Add_nl == _Add_newline::_Yes) { + *_Dest_ptr = '\n'; + ++_Num_chars_written; } - (void) _STD _Fmt_write(_Ctx.out(), _Closing_bracket); + return _Num_chars_written; + }); + + return _Unescaped_str; +} + +template +_NODISCARD constexpr const _CharT* _Parse_fill_align_and_width_specs( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + if (_Begin == _End || *_Begin == '}') { + return _Begin; } - template <_RANGES input_range _Range, class _FormatContext> - void _Format_as_string(_Range&& _Rng, _FormatContext& _Ctx, const bool _Debug) const { - if constexpr (_RANGES contiguous_range<_Range>) { - const auto _Size = _STD _To_unsigned_like(_RANGES distance(_Rng)); + _Begin = _Parse_align(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } - if (!_STD in_range(_Size)) [[unlikely]] { - _Throw_format_error("Formatted range is too long."); - } + return _Parse_width(_Begin, _End, _Callbacks); +} - formatter, _CharT> _String_view_formatter; - if (_Debug) { - _String_view_formatter.set_debug_format(); - } +template +struct _Fill_align_and_width_formatter { +public: + _NODISCARD constexpr auto _Parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + _Fill_align_and_width_specs_setter<_CharT> _Callback{_Specs, _Parse_ctx}; + const auto _It = + _Parse_fill_align_and_width_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); + if (_It != _Parse_ctx._Unchecked_end() && *_It != '}') { + _Throw_format_error("Missing '}' in format string."); + } - const basic_string_view<_CharT> _Str(_STD to_address(_RANGES begin(_Rng)), static_cast(_Size)); - _String_view_formatter.format(_Str, _Ctx); - } else { - using _String = basic_string<_CharT>; - formatter<_String, _CharT> _String_formatter; - if (_Debug) { - _String_formatter.set_debug_format(); - } + return _Parse_ctx.begin() + (_It - _Parse_ctx._Unchecked_begin()); + } - _String_formatter.format(_String{from_range, _Rng}, _Ctx); + template + _NODISCARD constexpr auto _Format( + _FormatContext& _Format_ctx, const int _Width, _Fmt_align _Default_align, _Func&& _Fn) const { + _Fill_align_and_width_specs _Format_specs = _Specs; + if (_Specs._Dynamic_width_index >= 0) { + _Format_specs._Width = + _Get_dynamic_specs<_Width_checker>(_Format_ctx.arg(static_cast(_Specs._Dynamic_width_index))); } + + return _Write_aligned(_Format_ctx.out(), _Width, _Format_specs, _Default_align, _STD forward<_Func>(_Fn)); } + +private: + _Fill_align_and_width_specs<_CharT> _Specs; }; #endif // _HAS_CXX23 _STD_END From e0b8a3086f33fc4c56b5cff8263b817d85fcd0b1 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Sun, 9 Jun 2024 21:18:58 +0800 Subject: [PATCH 02/11] Implement `range-default-formatter` The following tests are derived from libc++'s test files: - `P2286R8_text_formatting_range_map` - `P2286R8_text_formatting_range_sequence` - `P2286R8_text_formatting_range_set` - `P2286R8_text_formatting_range_string` Note that libc++'s `range_formatter` is currently buggy on the `m` option, see LLVM-90196 and LLVM-94562. The test `P2286R8_text_formatting_tuple_disambiguation` covers formatting `pair`s and `tuple`s that are also ranges. Unblocks libcxx tests: - `std/containers/sequences/vector.bool/vector.bool.fmt/format.functions.format.pass.cpp` - `std/containers/sequences/vector.bool/vector.bool.fmt/format.functions.vformat.pass.cpp` - `std/utilities/format/format.range/format.range.fmtdef/parse.pass.cpp` - `std/utilities/format/format.range/format.range.fmtmap/format.functions.format.pass.cpp` - `std/utilities/format/format.range/format.range.fmtmap/format.functions.vformat.pass.cpp` - `std/utilities/format/format.range/format.range.fmtmap/parse.pass.cpp` - `std/utilities/format/format.range/format.range.fmtset/parse.pass.cpp` - `std/utilities/format/format.range/format.range.fmtstr/format.functions.format.pass.cpp` - `std/utilities/format/format.range/format.range.fmtstr/format.functions.vformat.pass.cpp` - `std/utilities/format/format.range/format.range.fmtstr/parse.pass.cpp` - `std/input.output/iostream.format/print.fun/includes.compile.pass.cpp` --- stl/inc/__msvc_formatter.hpp | 13 +- stl/inc/format | 162 ++ tests/libcxx/expected_results.txt | 35 +- tests/std/test.lst | 5 + .../test.compile.pass.cpp | 20 + .../P2286R8_text_formatting_range_map/env.lst | 4 + .../test.cpp | 1100 +++++++++ .../env.lst | 4 + .../test.cpp | 1991 +++++++++++++++++ .../P2286R8_text_formatting_range_set/env.lst | 4 + .../test.cpp | 1773 +++++++++++++++ .../env.lst | 4 + .../test.cpp | 563 +++++ .../env.lst | 4 + .../test.cpp | 142 ++ 15 files changed, 5801 insertions(+), 23 deletions(-) create mode 100644 tests/std/tests/P2286R8_text_formatting_range_map/env.lst create mode 100644 tests/std/tests/P2286R8_text_formatting_range_map/test.cpp create mode 100644 tests/std/tests/P2286R8_text_formatting_range_sequence/env.lst create mode 100644 tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp create mode 100644 tests/std/tests/P2286R8_text_formatting_range_set/env.lst create mode 100644 tests/std/tests/P2286R8_text_formatting_range_set/test.cpp create mode 100644 tests/std/tests/P2286R8_text_formatting_range_string/env.lst create mode 100644 tests/std/tests/P2286R8_text_formatting_range_string/test.cpp create mode 100644 tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/env.lst create mode 100644 tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/test.cpp diff --git a/stl/inc/__msvc_formatter.hpp b/stl/inc/__msvc_formatter.hpp index cda1abb1e65..942393699fa 100644 --- a/stl/inc/__msvc_formatter.hpp +++ b/stl/inc/__msvc_formatter.hpp @@ -47,6 +47,9 @@ #include #include #include +#if _HAS_CXX23 +#include +#endif // _HAS_CXX23 #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) @@ -305,11 +308,19 @@ constexpr range_format format_kind<_Rng> = []() consteval { } }(); -// Specializations for pairs and tuples are forward-declared to avoid any risk of using the disabled primary template. +// Specializations for pairs, tuples and ranges are forward-declared to avoid any risk of using the disabled primary +// template. // Per LWG-3997, `_CharT` in library-provided `formatter` specializations is // constrained to character types supported by `format`. +template +concept _Formatting_enabled_range = format_kind<_Rng> != range_format::disabled; + +template <_RANGES input_range _Rng, _Format_supported_charT _CharT> + requires _Formatting_enabled_range<_Rng> +struct formatter<_Rng, _CharT>; + _EXPORT_STD template struct pair; diff --git a/stl/inc/format b/stl/inc/format index abe80510d91..beaf52712dd 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -4247,6 +4247,150 @@ private: } }; +template +concept _Const_formattable_range = + _RANGES input_range && formattable<_RANGES range_reference_t, _CharT>; + +template +using _Fmt_maybe_const = conditional_t<_Const_formattable_range<_Rng, _CharT>, const _Rng, _Rng>; + +template +struct _Range_default_formatter; + +template <_RANGES input_range _Rng, class _CharT> +struct _Range_default_formatter { +private: + using _Range_type = _Fmt_maybe_const<_Rng, _CharT>; + + range_formatter>, _CharT> _Underlying; + +public: + constexpr void set_separator(const basic_string_view<_CharT> _Sep) noexcept { + _Underlying.set_separator(_Sep); + } + + constexpr void set_brackets( + const basic_string_view<_CharT> _Opening, const basic_string_view<_CharT> _Closing) noexcept { + _Underlying.set_brackets(_Opening, _Closing); + } + + template + constexpr _ParseContext::iterator parse(_ParseContext& _Ctx) { + return _Underlying.parse(_Ctx); + } + + template + _FormatContext::iterator format(_Range_type& _Elems, _FormatContext& _Ctx) const { + return _Underlying.format(_Elems, _Ctx); + } +}; + +template <_RANGES input_range _Rng, class _CharT> +struct _Range_default_formatter { +private: + using _Map_type = _Fmt_maybe_const<_Rng, _CharT>; + using _Element_type = remove_cvref_t<_RANGES range_reference_t<_Map_type>>; + + range_formatter<_Element_type, _CharT> _Underlying; + +public: + constexpr _Range_default_formatter() noexcept( + is_nothrow_default_constructible_v>) /* strengthened */ { + static_assert(_Is_two_tuple<_Element_type>, "the element type of the formatted range must be either pair " + "or tuple (N4981 [format.range.fmtmap]/1)"); + + _Underlying.set_brackets(_STATICALLY_WIDEN(_CharT, "{"), _STATICALLY_WIDEN(_CharT, "}")); + _Underlying.underlying().set_brackets({}, {}); + _Underlying.underlying().set_separator(_STATICALLY_WIDEN(_CharT, ": ")); + } + + template + constexpr _ParseContext::iterator parse(_ParseContext& _Ctx) { + return _Underlying.parse(_Ctx); + } + + template + _FormatContext::iterator format(_Map_type& _Rx, _FormatContext& _Ctx) const { + return _Underlying.format(_Rx, _Ctx); + } +}; + +template <_RANGES input_range _Rng, class _CharT> +struct _Range_default_formatter { +private: + using _Set_type = _Fmt_maybe_const<_Rng, _CharT>; // exposition only + range_formatter>, _CharT> _Underlying; + +public: + constexpr _Range_default_formatter() noexcept(is_nothrow_default_constructible_v< + range_formatter>, _CharT>>) /* strengthened */ { + _Underlying.set_brackets(_STATICALLY_WIDEN(_CharT, "{"), _STATICALLY_WIDEN(_CharT, "}")); + } + + template + constexpr _ParseContext::iterator parse(_ParseContext& _Ctx) { + return _Underlying.parse(_Ctx); + } + + template + _FormatContext::iterator format(_Set_type& _Rx, _FormatContext& _Ctx) const { + return _Underlying.format(_Rx, _Ctx); + } +}; + +template + requires (_Kind == range_format::string || _Kind == range_format::debug_string) +struct _Range_default_formatter<_Kind, _Rng, _CharT> { +private: + static_assert(is_same_v>, _CharT>, + "the element type of the formatted range must be the character type used in formatting " + "(N4981 [format.range.fmtstr]/1)"); + + using _Range_type = _Maybe_const<_RANGES input_range, _Rng>; + + formatter, _CharT> _Underlying; // avoid copy the string if possible + +public: + template + constexpr _ParseContext::iterator parse(_ParseContext& _Ctx) { + auto _Iter = _Underlying.parse(_Ctx); + if constexpr (_Kind == range_format::debug_string) { + _Underlying.set_debug_format(); + } + return _Iter; + } + + template + _FormatContext::iterator format(_Range_type& _Rx, _FormatContext& _Ctx) const { + if constexpr (_RANGES contiguous_range<_Range_type>) { + const auto _Size = _STD _To_unsigned_like(_RANGES distance(_Rx)); + + if (!_STD in_range(_Size)) [[unlikely]] { + _Throw_format_error("Formatted range is too long."); + } + + const basic_string_view<_CharT> _Str(_STD to_address(_RANGES begin(_Rx)), static_cast(_Size)); + return _Underlying.format(_Str, _Ctx); + } else { + return _Underlying.format(basic_string<_CharT>{from_range, _Rx}, _Ctx); + } + } +}; + +// the deleted default constructor makes it "disabled" as per N4981 [format.formatter.spec]/5 + +template <_RANGES input_range _Rng, _Format_supported_charT _CharT> + requires _Formatting_enabled_range<_Rng> +struct formatter<_Rng, _CharT> { + formatter() = delete; + formatter(const formatter&) = delete; + formatter& operator=(const formatter&) = delete; +}; + +template <_RANGES input_range _Rng, _Format_supported_charT _CharT> + requires _Formatting_enabled_range<_Rng> && formattable<_RANGES range_reference_t<_Rng>, _CharT> +struct formatter<_Rng, _CharT> : _Range_default_formatter, _Rng, _CharT> {}; + enum class _Fmt_tuple_type : uint8_t { _None, _Key_value, _No_brackets }; template @@ -4497,6 +4641,24 @@ public: } }; +// specializations for tuple-like types that are input ranges and not formattable as tuples + +template <_Format_supported_charT _CharT, class _Ty1, class _Ty2> + requires (!formattable<_Ty1, _CharT> || !formattable<_Ty2, _CharT>) + // TRANSITION, clang-format, () should be redundant + && (_RANGES input_range>) && _Formatting_enabled_range> + && formattable<_RANGES range_reference_t>, _CharT> +struct _Tuple_formatter_base, _CharT> + : _Range_default_formatter>, pair<_Ty1, _Ty2>, _CharT> {}; + +template <_Format_supported_charT _CharT, class... _Types> + requires ((!formattable<_Types, _CharT>) || ...) + // TRANSITION, clang-format, () should be redundant + && (_RANGES input_range>) && _Formatting_enabled_range> + && formattable<_RANGES range_reference_t>, _CharT> +struct _Tuple_formatter_base, _CharT> + : _Range_default_formatter>, tuple<_Types...>, _CharT> {}; + // Per LWG-3997, `_CharT` in library-provided `formatter` specializations is // constrained to character types supported by `format`. diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 699e522f93b..6f35c3e751f 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -26,6 +26,12 @@ std/time/time.syn/formatter.year_month_weekday.pass.cpp:1 FAIL std/utilities/memory/specialized.algorithms/uninitialized.copy/uninitialized_copy.pass.cpp FAIL std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp FAIL +# LLVM-90196: [libc++][format] Formatting range with m range-type is incorrect +std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp FAIL +std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp FAIL +std/utilities/format/format.range/format.range.fmtset/format.functions.format.pass.cpp FAIL +std/utilities/format/format.range/format.range.fmtset/format.functions.vformat.pass.cpp FAIL + # Non-Standard regex behavior. # "It seems likely that the test is still non-conforming due to how libc++ handles the 'w' character class." std/re/re.traits/lookup_classname.pass.cpp FAIL @@ -211,29 +217,7 @@ std/containers/container.adaptors/container.adaptors.format/format.functions.vfo std/containers/container.adaptors/container.adaptors.format/format.pass.cpp FAIL std/containers/container.adaptors/container.adaptors.format/parse.pass.cpp FAIL std/containers/container.adaptors/container.adaptors.format/types.compile.pass.cpp FAIL -std/containers/sequences/vector.bool/vector.bool.fmt/format.functions.format.pass.cpp FAIL -std/containers/sequences/vector.bool/vector.bool.fmt/format.functions.vformat.pass.cpp FAIL -std/containers/sequences/vector.bool/vector.bool.fmt/format.pass.cpp FAIL -std/input.output/iostream.format/print.fun/includes.compile.pass.cpp FAIL std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtdef/format.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtdef/parse.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtdef/set_brackets.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtdef/set_separator.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtmap/format.functions.format.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtmap/format.functions.vformat.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtmap/format.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtmap/parse.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtset/format.functions.format.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtset/format.functions.vformat.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtset/format.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtset/parse.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtstr/format.functions.format.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtstr/format.functions.vformat.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtstr/format.pass.cpp FAIL -std/utilities/format/format.range/format.range.fmtstr/parse.pass.cpp FAIL -std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp FAIL -std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp FAIL # *** MISSING COMPILER FEATURES *** @@ -961,6 +945,7 @@ std/input.output/string.streams/stringstream/stringstream.members/str.allocator_ # These formatter tests need to create basic_format_contexts, so test_format_context.h # says "Please create a vendor specific version of the test functions". # If we do, some of these tests should be able to work. +std/containers/sequences/vector.bool/vector.bool.fmt/format.pass.cpp FAIL std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp FAIL std/utilities/format/format.formatter/format.context/format.context/advance_to.pass.cpp FAIL std/utilities/format/format.formatter/format.context/format.context/arg.pass.cpp FAIL @@ -982,6 +967,12 @@ std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_i std/utilities/format/format.range/format.range.formatter/format.pass.cpp FAIL std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp FAIL std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp FAIL +std/utilities/format/format.range/format.range.fmtdef/format.pass.cpp FAIL +std/utilities/format/format.range/format.range.fmtdef/set_brackets.pass.cpp FAIL +std/utilities/format/format.range/format.range.fmtdef/set_separator.pass.cpp FAIL +std/utilities/format/format.range/format.range.fmtmap/format.pass.cpp FAIL +std/utilities/format/format.range/format.range.fmtset/format.pass.cpp FAIL +std/utilities/format/format.range/format.range.fmtstr/format.pass.cpp FAIL std/utilities/format/format.tuple/format.pass.cpp FAIL # Not analyzed. Apparent false positives from static analysis where it thinks that array indexing is out of bounds. diff --git a/tests/std/test.lst b/tests/std/test.lst index 3893043f277..89f6bd72ac7 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -619,7 +619,12 @@ tests\P2286R8_text_formatting_escaping_legacy_text_encoding tests\P2286R8_text_formatting_escaping_utf8 tests\P2286R8_text_formatting_formattable tests\P2286R8_text_formatting_range_formatter +tests\P2286R8_text_formatting_range_map +tests\P2286R8_text_formatting_range_sequence +tests\P2286R8_text_formatting_range_set +tests\P2286R8_text_formatting_range_string tests\P2286R8_text_formatting_tuple +tests\P2286R8_text_formatting_tuple_disambiguation tests\P2286R8_text_formatting_vector_bool_reference tests\P2302R4_ranges_alg_contains tests\P2302R4_ranges_alg_contains_subrange diff --git a/tests/std/tests/P2286R8_text_formatting_formattable/test.compile.pass.cpp b/tests/std/tests/P2286R8_text_formatting_formattable/test.compile.pass.cpp index 83897c08b72..3dd6fa18038 100644 --- a/tests/std/tests/P2286R8_text_formatting_formattable/test.compile.pass.cpp +++ b/tests/std/tests/P2286R8_text_formatting_formattable/test.compile.pass.cpp @@ -213,6 +213,26 @@ void test_P2286_vector_bool() { // Tests for P2286 Formatting ranges template void test_P2286() { + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + + assert_is_formattable, CharT>(); + + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); assert_is_formattable, CharT>(); diff --git a/tests/std/tests/P2286R8_text_formatting_range_map/env.lst b/tests/std/tests/P2286R8_text_formatting_range_map/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_range_map/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp new file mode 100644 index 00000000000..49541648c77 --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp @@ -0,0 +1,1100 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// derived from libc++'s test files: +// * support/format.functions.common.h +// * std/utilities/format/format.range/format.range.fmtmap/format.functions.tests.h +// * std/utilities/format/format.range/format.range.fmtmap/format.functions.format.pass.cpp +// * std/utilities/format/format.range/format.range.fmtmap/format.functions.vformat.pass.cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; + +#define CSTR(Str) TYPED_LITERAL(CharT, Str) +#define STR(Str) basic_string(CSTR(Str)) +#define SV(Str) basic_string_view(CSTR(Str)) + +namespace detail { + consteval string_view get_format_types() noexcept { + return "aAbBcdeEfFgGopPsxX?"; + } + + template + basic_string get_colons() { + return basic_string(N, CharT(':')); + } + + template + vector> fmt_invalid_types(string_view valid) { + vector> result; + if constexpr (is_permissive_v) { + for (const char c : get_format_types()) { + if (valid.find(c) == string_view::npos) { + result.push_back(format(SV("{{{}{}}}"), get_colons(), c)); + } + } + } else { + // ranges::to is not available in C++20. + ranges::copy(get_format_types() | views::filter([&](char type) { + return valid.find(type) == string_view::npos; + }) | views::transform([&](char type) { return format(SV("{{{}{}}}"), get_colons(), type); }), + back_inserter(result)); + } + return result; + } +} // namespace detail + +// Creates format string for the invalid types. +// +// valid contains a list of types that are valid. +// +// The return value is a collection of basic_strings, instead of +// basic_string_views since the values are temporaries. +template +vector> fmt_invalid_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +// Like fmt_invalid_types but when the format spec is for an underlying formatter. +template +vector> fmt_invalid_nested_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +template +struct context {}; + +template <> +struct context { + using type = format_context; +}; + +template <> +struct context { + using type = wformat_context; +}; + +template +using context_t = context::type; + +// A user-defined type used to test the handle formatter. +enum class status : uint16_t { foo = 0xAAAA, bar = 0x5555, foobar = 0xAA55 }; + +// The formatter for a user-defined type used to test the handle formatter. +template +struct formatter { + // During the 2023 Issaquah meeting LEWG made it clear a formatter is + // required to call its parse function. LWG3892 Adds the wording for that + // requirement. Therefore this formatter is initialized in an invalid state. + // A call to parse sets it in a valid state and a call to format validates + // the state. + int type = -1; + + constexpr auto parse(basic_format_parse_context& parse_ctx) -> decltype(parse_ctx.begin()) { + auto begin = parse_ctx.begin(); + auto end = parse_ctx.end(); + type = 0; + if (begin == end) { + return begin; + } + + switch (*begin) { + case CharT('x'): + break; + case CharT('X'): + type = 1; + break; + case CharT('s'): + type = 2; + break; + case CharT('}'): + return begin; + default: + throw_format_error("The type option contains an invalid value for a status formatting argument"); + } + + ++begin; + if (begin != end && *begin != CharT('}')) { + throw_format_error("The format specifier should consume the input or end with a '}'"); + } + + return begin; + } + + template + auto format(status s, basic_format_context& ctx) const -> decltype(ctx.out()) { + const char* names[] = {"foo", "bar", "foobar"}; + char buffer[7]; + const char* pbegin = names[0]; + const char* pend = names[0]; + switch (type) { + case -1: + throw_format_error("The formatter's parse function has not been called."); + + case 0: + pbegin = buffer; + buffer[0] = '0'; + buffer[1] = 'x'; + pend = to_chars(&buffer[2], end(buffer), static_cast(s), 16).ptr; + buffer[6] = '\0'; + break; + + case 1: + pbegin = buffer; + buffer[0] = '0'; + buffer[1] = 'X'; + pend = to_chars(&buffer[2], end(buffer), static_cast(s), 16).ptr; + transform(static_cast(&buffer[2]), pend, &buffer[2], + [](char c) { return static_cast(toupper(c)); }); + buffer[6] = '\0'; + break; + + case 2: + switch (s) { + case status::foo: + pbegin = names[0]; + break; + case status::bar: + pbegin = names[1]; + break; + case status::foobar: + pbegin = names[2]; + break; + } + pend = pbegin + strlen(pbegin); + break; + } + +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(push) +#pragma warning(disable : 4365) +#endif // !defined(__clang__) && !defined(__EDG__) + return copy(pbegin, pend, ctx.out()); +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(pop) +#endif // !defined(__clang__) && !defined(__EDG__) + } + +private: + [[noreturn]] void throw_format_error(const char* s) const { + throw format_error(s); + } +}; + + +// +// Char +// + +template +void test_char(TestFunction check, ExceptionTest check_exception) { + map input{{CharT('a'), CharT('A')}, {CharT('c'), CharT('C')}, {CharT('b'), CharT('B')}}; + + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{}"), input); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}^42"), SV("{}^42"), input); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'} "), SV("{:35}"), input); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}*****"), SV("{:*<35}"), input); + check(SV("__{'a': 'A', 'b': 'B', 'c': 'C'}___"), SV("{:_^35}"), input); + check(SV("#####{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{:#>35}"), input); + + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'} "), SV("{:{}}"), input, 35); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}*****"), SV("{:*<{}}"), input, 35); + check(SV("__{'a': 'A', 'b': 'B', 'c': 'C'}___"), SV("{:_^{}}"), input, 35); + check(SV("#####{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{:#>{}}"), input, 35); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__'a': 'A', 'b': 'B', 'c': 'C'___"), SV("{:_^33n}"), input); + + // *** type *** + check(SV("__{'a': 'A', 'b': 'B', 'c': 'C'}___"), SV("{:_^35m}"), input); // the m type does the same as the default. + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + + check(SV("{'a': 'A' , 'b': 'B' , 'c': 'C' }"), SV("{::13}"), input); + check(SV("{'a': 'A'*****, 'b': 'B'*****, 'c': 'C'*****}"), SV("{::*<13}"), input); + check(SV("{__'a': 'A'___, __'b': 'B'___, __'c': 'C'___}"), SV("{::_^13}"), input); + check(SV("{#####'a': 'A', #####'b': 'B', #####'c': 'C'}"), SV("{::#>13}"), input); + + check(SV("{'a': 'A' , 'b': 'B' , 'c': 'C' }"), SV("{::{}}"), input, 13); + check(SV("{'a': 'A'*****, 'b': 'B'*****, 'c': 'C'*****}"), SV("{::*<{}}"), input, 13); + check(SV("{__'a': 'A'___, __'b': 'B'___, __'c': 'C'___}"), SV("{::_^{}}"), input, 13); + check(SV("{#####'a': 'A', #####'b': 'B', #####'c': 'C'}"), SV("{::#>{}}"), input, 13); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{::m}"), input); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{::n}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{###'a': 'A', ###'b': 'B', ###'c': 'C'}^^^"), SV("{:^^44:#>11}"), input); + check(SV("^^{###'a': 'A', ###'b': 'B', ###'c': 'C'}^^^"), SV("{:^^{}:#>11}"), input, 44); + check(SV("^^{###'a': 'A', ###'b': 'B', ###'c': 'C'}^^^"), SV("{:^^{}:#>{}}"), input, 44, 11); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>11}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 44); +} + +// +// char -> wchar_t +// + +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +template +void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { + map input{{'a', 'A'}, {'c', 'C'}, {'b', 'B'}}; + + using CharT = wchar_t; + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{}"), input); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}^42"), SV("{}^42"), input); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'} "), SV("{:35}"), input); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}*****"), SV("{:*<35}"), input); + check(SV("__{'a': 'A', 'b': 'B', 'c': 'C'}___"), SV("{:_^35}"), input); + check(SV("#####{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{:#>35}"), input); + + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'} "), SV("{:{}}"), input, 35); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}*****"), SV("{:*<{}}"), input, 35); + check(SV("__{'a': 'A', 'b': 'B', 'c': 'C'}___"), SV("{:_^{}}"), input, 35); + check(SV("#####{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{:#>{}}"), input, 35); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__'a': 'A', 'b': 'B', 'c': 'C'___"), SV("{:_^33n}"), input); + + // *** type *** + check(SV("__{'a': 'A', 'b': 'B', 'c': 'C'}___"), SV("{:_^35m}"), input); // the m type does the same as the default. + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{'a': 'A' , 'b': 'B' , 'c': 'C' }"), SV("{::13}"), input); + check(SV("{'a': 'A'*****, 'b': 'B'*****, 'c': 'C'*****}"), SV("{::*<13}"), input); + check(SV("{__'a': 'A'___, __'b': 'B'___, __'c': 'C'___}"), SV("{::_^13}"), input); + check(SV("{#####'a': 'A', #####'b': 'B', #####'c': 'C'}"), SV("{::#>13}"), input); + + check(SV("{'a': 'A' , 'b': 'B' , 'c': 'C' }"), SV("{::{}}"), input, 13); + check(SV("{'a': 'A'*****, 'b': 'B'*****, 'c': 'C'*****}"), SV("{::*<{}}"), input, 13); + check(SV("{__'a': 'A'___, __'b': 'B'___, __'c': 'C'___}"), SV("{::_^{}}"), input, 13); + check(SV("{#####'a': 'A', #####'b': 'B', #####'c': 'C'}"), SV("{::#>{}}"), input, 13); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{::m}"), input); + check(SV("{'a': 'A', 'b': 'B', 'c': 'C'}"), SV("{::n}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{###'a': 'A', ###'b': 'B', ###'c': 'C'}^^^"), SV("{:^^44:#>11}"), input); + check(SV("^^{###'a': 'A', ###'b': 'B', ###'c': 'C'}^^^"), SV("{:^^{}:#>11}"), input, 44); + check(SV("^^{###'a': 'A', ###'b': 'B', ###'c': 'C'}^^^"), SV("{:^^{}:#>{}}"), input, 44, 11); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>11}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 44); +} +#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +// +// Bool +// +template +void test_bool(TestFunction check, ExceptionTest check_exception) { + // duplicates are stored in order of insertion + multimap input{{true, 42}, {false, 0}, {true, 1}}; + + check(SV("{false: 0, true: 42, true: 1}"), SV("{}"), input); + check(SV("{false: 0, true: 42, true: 1}^42"), SV("{}^42"), input); + check(SV("{false: 0, true: 42, true: 1}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{false: 0, true: 42, true: 1} "), SV("{:34}"), input); + check(SV("{false: 0, true: 42, true: 1}*****"), SV("{:*<34}"), input); + check(SV("__{false: 0, true: 42, true: 1}___"), SV("{:_^34}"), input); + check(SV("#####{false: 0, true: 42, true: 1}"), SV("{:#>34}"), input); + + check(SV("{false: 0, true: 42, true: 1} "), SV("{:{}}"), input, 34); + check(SV("{false: 0, true: 42, true: 1}*****"), SV("{:*<{}}"), input, 34); + check(SV("__{false: 0, true: 42, true: 1}___"), SV("{:_^{}}"), input, 34); + check(SV("#####{false: 0, true: 42, true: 1}"), SV("{:#>{}}"), input, 34); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__false: 0, true: 42, true: 1___"), SV("{:_^32n}"), input); + + // *** type *** + check(SV("__{false: 0, true: 42, true: 1}___"), SV("{:_^34m}"), input); // the m type does the same as the default. + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{false: 0 , true: 42 , true: 1 }"), SV("{::10}"), input); + check(SV("{false: 0**, true: 42**, true: 1***}"), SV("{::*<10}"), input); + check(SV("{_false: 0_, _true: 42_, _true: 1__}"), SV("{::_^10}"), input); + check(SV("{##false: 0, ##true: 42, ###true: 1}"), SV("{::#>10}"), input); + + check(SV("{false: 0 , true: 42 , true: 1 }"), SV("{::{}}"), input, 10); + check(SV("{false: 0**, true: 42**, true: 1***}"), SV("{::*<{}}"), input, 10); + check(SV("{_false: 0_, _true: 42_, _true: 1__}"), SV("{::_^{}}"), input, 10); + check(SV("{##false: 0, ##true: 42, ###true: 1}"), SV("{::#>{}}"), input, 10); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{##false: 0, ##true: 42, ###true: 1}^^^"), SV("{:^^41:#>10}"), input); + check(SV("^^{##false: 0, ##true: 42, ###true: 1}^^^"), SV("{:^^{}:#>10}"), input, 41); + check(SV("^^{##false: 0, ##true: 42, ###true: 1}^^^"), SV("{:^^{}:#>{}}"), input, 41, 10); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>10}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 41); +} + +// +// Integral +// + +template +void test_int(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("{-42: 42, 1: -1, 42: -42}"), SV("{}"), input); + check(SV("{-42: 42, 1: -1, 42: -42}^42"), SV("{}^42"), input); + check(SV("{-42: 42, 1: -1, 42: -42}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{-42: 42, 1: -1, 42: -42} "), SV("{:30}"), input); + check(SV("{-42: 42, 1: -1, 42: -42}*****"), SV("{:*<30}"), input); + check(SV("__{-42: 42, 1: -1, 42: -42}___"), SV("{:_^30}"), input); + check(SV("#####{-42: 42, 1: -1, 42: -42}"), SV("{:#>30}"), input); + + check(SV("{-42: 42, 1: -1, 42: -42} "), SV("{:{}}"), input, 30); + check(SV("{-42: 42, 1: -1, 42: -42}*****"), SV("{:*<{}}"), input, 30); + check(SV("__{-42: 42, 1: -1, 42: -42}___"), SV("{:_^{}}"), input, 30); + check(SV("#####{-42: 42, 1: -1, 42: -42}"), SV("{:#>{}}"), input, 30); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__-42: 42, 1: -1, 42: -42___"), SV("{:_^28n}"), input); + + // *** type *** + check(SV("__{-42: 42, 1: -1, 42: -42}___"), SV("{:_^30m}"), input); // the m type does the same as the default. + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{-42: 42 , 1: -1 , 42: -42 }"), SV("{::10}"), input); + check(SV("{-42: 42***, 1: -1*****, 42: -42***}"), SV("{::*<10}"), input); + check(SV("{_-42: 42__, __1: -1___, _42: -42__}"), SV("{::_^10}"), input); + check(SV("{###-42: 42, #####1: -1, ###42: -42}"), SV("{::#>10}"), input); + + check(SV("{-42: 42 , 1: -1 , 42: -42 }"), SV("{::{}}"), input, 10); + check(SV("{-42: 42***, 1: -1*****, 42: -42***}"), SV("{::*<{}}"), input, 10); + check(SV("{_-42: 42__, __1: -1___, _42: -42__}"), SV("{::_^{}}"), input, 10); + check(SV("{###-42: 42, #####1: -1, ###42: -42}"), SV("{::#>{}}"), input, 10); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{###-42: 42, #####1: -1, ###42: -42}^^^"), SV("{:^^41:#>10}"), input); + check(SV("^^{###-42: 42, #####1: -1, ###42: -42}^^^"), SV("{:^^{}:#>10}"), input, 41); + check(SV("^^{###-42: 42, #####1: -1, ###42: -42}^^^"), SV("{:^^{}:#>{}}"), input, 41, 10); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>10}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 41); +} + +template +void test_int(TestFunction check, ExceptionTest check_exception) { + test_int(check, check_exception, map{{1, -1}, {42, -42}, {-42, 42}}); +} + +// +// Floating point +// + +template +void test_floating_point(TestFunction check, ExceptionTest check_exception) { + map input{{1.0, -1.0}, {-42, 42}}; + + check(SV("{-42: 42, 1: -1}"), SV("{}"), input); + check(SV("{-42: 42, 1: -1}^42"), SV("{}^42"), input); + check(SV("{-42: 42, 1: -1}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{-42: 42, 1: -1} "), SV("{:21}"), input); + check(SV("{-42: 42, 1: -1}*****"), SV("{:*<21}"), input); + check(SV("__{-42: 42, 1: -1}___"), SV("{:_^21}"), input); + check(SV("#####{-42: 42, 1: -1}"), SV("{:#>21}"), input); + + check(SV("{-42: 42, 1: -1} "), SV("{:{}}"), input, 21); + check(SV("{-42: 42, 1: -1}*****"), SV("{:*<{}}"), input, 21); + check(SV("__{-42: 42, 1: -1}___"), SV("{:_^{}}"), input, 21); + check(SV("#####{-42: 42, 1: -1}"), SV("{:#>{}}"), input, 21); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__-42: 42, 1: -1___"), SV("{:_^19n}"), input); + + // *** type *** + check(SV("__{-42: 42, 1: -1}___"), SV("{:_^21m}"), input); // the m type does the same as the default. + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{-42: 42 , 1: -1 }"), SV("{::10}"), input); + check(SV("{-42: 42***, 1: -1*****}"), SV("{::*<10}"), input); + check(SV("{_-42: 42__, __1: -1___}"), SV("{::_^10}"), input); + check(SV("{###-42: 42, #####1: -1}"), SV("{::#>10}"), input); + + check(SV("{-42: 42 , 1: -1 }"), SV("{::{}}"), input, 10); + check(SV("{-42: 42***, 1: -1*****}"), SV("{::*<{}}"), input, 10); + check(SV("{_-42: 42__, __1: -1___}"), SV("{::_^{}}"), input, 10); + check(SV("{###-42: 42, #####1: -1}"), SV("{::#>{}}"), input, 10); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{###-42: 42, #####1: -1}^^^"), SV("{:^^29:#>10}"), input); + check(SV("^^{###-42: 42, #####1: -1}^^^"), SV("{:^^{}:#>10}"), input, 29); + check(SV("^^{###-42: 42, #####1: -1}^^^"), SV("{:^^{}:#>{}}"), input, 29, 10); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>10}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 29); +} + +// +// Pointer +// + +template +void test_pointer(TestFunction check, ExceptionTest check_exception) { + unordered_map input{{0, 0}}; + + check(SV("{0x0: 0x0}"), SV("{}"), input); + check(SV("{0x0: 0x0}^42"), SV("{}^42"), input); + check(SV("{0x0: 0x0}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{0x0: 0x0} "), SV("{:15}"), input); + check(SV("{0x0: 0x0}*****"), SV("{:*<15}"), input); + check(SV("__{0x0: 0x0}___"), SV("{:_^15}"), input); + check(SV("#####{0x0: 0x0}"), SV("{:#>15}"), input); + + check(SV("{0x0: 0x0} "), SV("{:{}}"), input, 15); + check(SV("{0x0: 0x0}*****"), SV("{:*<{}}"), input, 15); + check(SV("__{0x0: 0x0}___"), SV("{:_^{}}"), input, 15); + check(SV("#####{0x0: 0x0}"), SV("{:#>{}}"), input, 15); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__0x0: 0x0___"), SV("{:_^13n}"), input); + + // *** type *** + check(SV("__{0x0: 0x0}___"), SV("{:_^15m}"), input); // the m type does the same as the default. + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{0x0: 0x0 }"), SV("{::13}"), input); + check(SV("{0x0: 0x0*****}"), SV("{::*<13}"), input); + check(SV("{__0x0: 0x0___}"), SV("{::_^13}"), input); + check(SV("{#####0x0: 0x0}"), SV("{::#>13}"), input); + + check(SV("{0x0: 0x0 }"), SV("{::{}}"), input, 13); + check(SV("{0x0: 0x0*****}"), SV("{::*<{}}"), input, 13); + check(SV("{__0x0: 0x0___}"), SV("{::_^{}}"), input, 13); + check(SV("{#####0x0: 0x0}"), SV("{::#>{}}"), input, 13); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{###0x0: 0x0}^^^"), SV("{:^^18:#>11}"), input); + check(SV("^^{###0x0: 0x0}^^^"), SV("{:^^{}:#>11}"), input, 18); + check(SV("^^{###0x0: 0x0}^^^"), SV("{:^^{}:#>{}}"), input, 18, 11); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>11}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 18); +} + +// +// String +// + +template +void test_string(TestFunction check, ExceptionTest check_exception) { + map, basic_string> input{{STR("hello"), STR("HELLO")}, {STR("world"), STR("WORLD")}}; + + check(SV(R"({"hello": "HELLO", "world": "WORLD"})"), SV("{}"), input); + check(SV(R"({"hello": "HELLO", "world": "WORLD"}^42)"), SV("{}^42"), input); + check(SV(R"({"hello": "HELLO", "world": "WORLD"}^42)"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"({"hello": "HELLO", "world": "WORLD"} )"), SV("{:41}"), input); + check(SV(R"({"hello": "HELLO", "world": "WORLD"}*****)"), SV("{:*<41}"), input); + check(SV(R"(__{"hello": "HELLO", "world": "WORLD"}___)"), SV("{:_^41}"), input); + check(SV(R"(#####{"hello": "HELLO", "world": "WORLD"})"), SV("{:#>41}"), input); + + check(SV(R"({"hello": "HELLO", "world": "WORLD"} )"), SV("{:{}}"), input, 41); + check(SV(R"({"hello": "HELLO", "world": "WORLD"}*****)"), SV("{:*<{}}"), input, 41); + check(SV(R"(__{"hello": "HELLO", "world": "WORLD"}___)"), SV("{:_^{}}"), input, 41); + check(SV(R"(#####{"hello": "HELLO", "world": "WORLD"})"), SV("{:#>{}}"), input, 41); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV(R"(__"hello": "HELLO", "world": "WORLD"___)"), SV("{:_^39n}"), input); + + // *** type *** + check(SV(R"(__{"hello": "HELLO", "world": "WORLD"}___)"), SV("{:_^41m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV(R"({"hello": "HELLO" , "world": "WORLD" })"), SV("{::21}"), input); + check(SV(R"({"hello": "HELLO"*****, "world": "WORLD"*****})"), SV("{::*<21}"), input); + check(SV(R"({__"hello": "HELLO"___, __"world": "WORLD"___})"), SV("{::_^21}"), input); + check(SV(R"({#####"hello": "HELLO", #####"world": "WORLD"})"), SV("{::#>21}"), input); + + check(SV(R"({"hello": "HELLO" , "world": "WORLD" })"), SV("{::{}}"), input, 21); + check(SV(R"({"hello": "HELLO"*****, "world": "WORLD"*****})"), SV("{::*<{}}"), input, 21); + check(SV(R"({__"hello": "HELLO"___, __"world": "WORLD"___})"), SV("{::_^{}}"), input, 21); + check(SV(R"({#####"hello": "HELLO", #####"world": "WORLD"})"), SV("{::#>{}}"), input, 21); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + + check(SV(R"(^^{#####"hello": "HELLO", #####"world": "WORLD"}^^^)"), SV("{:^^51:#>21}"), input); + check(SV(R"(^^{#####"hello": "HELLO", #####"world": "WORLD"}^^^)"), SV("{:^^{}:#>21}"), input, 51); + check(SV(R"(^^{#####"hello": "HELLO", #####"world": "WORLD"}^^^)"), SV("{:^^{}:#>{}}"), input, 51, 21); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>21}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 51); +} + +// +// Handle +// + +template +void test_status(TestFunction check, ExceptionTest check_exception) { + unordered_multimap input{{status::foobar, status::foo}, {status::foobar, status::bar}}; + + check(SV("{0xaa55: 0xaaaa, 0xaa55: 0x5555}"), SV("{}"), input); + check(SV("{0xaa55: 0xaaaa, 0xaa55: 0x5555}^42"), SV("{}^42"), input); + check(SV("{0xaa55: 0xaaaa, 0xaa55: 0x5555}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{0xaa55: 0xaaaa, 0xaa55: 0x5555} "), SV("{:37}"), input); + check(SV("{0xaa55: 0xaaaa, 0xaa55: 0x5555}*****"), SV("{:*<37}"), input); + check(SV("__{0xaa55: 0xaaaa, 0xaa55: 0x5555}___"), SV("{:_^37}"), input); + check(SV("#####{0xaa55: 0xaaaa, 0xaa55: 0x5555}"), SV("{:#>37}"), input); + + check(SV("{0xaa55: 0xaaaa, 0xaa55: 0x5555} "), SV("{:{}}"), input, 37); + check(SV("{0xaa55: 0xaaaa, 0xaa55: 0x5555}*****"), SV("{:*<{}}"), input, 37); + check(SV("__{0xaa55: 0xaaaa, 0xaa55: 0x5555}___"), SV("{:_^{}}"), input, 37); + check(SV("#####{0xaa55: 0xaaaa, 0xaa55: 0x5555}"), SV("{:#>{}}"), input, 37); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__0xaa55: 0xaaaa, 0xaa55: 0x5555___"), SV("{:_^35n}"), input); + + // *** type *** + check( + SV("__{0xaa55: 0xaaaa, 0xaa55: 0x5555}___"), SV("{:_^37}"), input); // the m type does the same as the default. + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + // Underlying can't have a format-spec +} + +// +// Adaptor +// + +class adaptor { + using adaptee = map; + +public: + using key_type = typename adaptee::key_type; + using mapped_type = typename adaptee::mapped_type; + using iterator = typename adaptee::iterator; + + iterator begin() { + return data_.begin(); + } + iterator end() { + return data_.end(); + } + + explicit adaptor(map&& data) : data_(move(data)) {} + +private: + adaptee data_; +}; + +static_assert(format_kind == range_format::map); + +template +void test_adaptor(TestFunction check, ExceptionTest check_exception) { + test_int(check, check_exception, adaptor{map{{1, -1}, {42, -42}, {-42, 42}}}); +} + +// +// Driver +// + +template +void format_tests(TestFunction check, ExceptionTest check_exception) { + test_char(check, check_exception); +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + if (same_as) { // avoid testing twice + test_char_to_wchar(check, check_exception); + } +#endif + test_bool(check, check_exception); + test_int(check, check_exception); + test_floating_point(check, check_exception); + test_pointer(check, check_exception); + test_string(check, check_exception); + + test_status(check, check_exception); + + test_adaptor(check, check_exception); +} + +auto test_format = [](basic_string_view expected, + type_identity_t> fmt, Args&&... args) { + basic_string out = format(fmt, forward(args)...); + assert(out == expected); +}; + +auto test_format_exception = [](string_view, basic_string_view, Args&&...) { + // After P2216 most exceptions thrown by format become ill-formed. + // Therefore this tests does nothing. +}; + +auto test_vformat = []( + basic_string_view expected, basic_string_view fmt, Args&&... args) { + basic_string out = vformat(fmt, make_format_args>(args...)); + assert(out == expected); +}; + +auto test_vformat_exception = []([[maybe_unused]] string_view what, + [[maybe_unused]] basic_string_view fmt, [[maybe_unused]] Args&&... args) { + try { + static_cast(vformat(fmt, make_format_args>(args...))); + assert(false); + } catch (const format_error&) { + } catch (...) { + assert(false); + } +}; + +int main() { + format_tests(test_format, test_format_exception); + format_tests(test_format, test_format_exception); + + format_tests(test_vformat, test_vformat_exception); + format_tests(test_vformat, test_vformat_exception); +} diff --git a/tests/std/tests/P2286R8_text_formatting_range_sequence/env.lst b/tests/std/tests/P2286R8_text_formatting_range_sequence/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_range_sequence/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp new file mode 100644 index 00000000000..ec93f259455 --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp @@ -0,0 +1,1991 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// derived from libc++'s test files: +// * support/format.functions.common.h +// * support/test_iterators.h +// * std/utilities/format/format.range/format.range.formatter/format.functions.tests.h +// * std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp +// * std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; + +#ifdef _DEBUG +#define DEFAULT_IDL_SETTING 2 +#else +#define DEFAULT_IDL_SETTING 0 +#endif + +#define CSTR(Str) TYPED_LITERAL(CharT, Str) +#define STR(Str) basic_string(CSTR(Str)) +#define SV(Str) basic_string_view(CSTR(Str)) + +namespace detail { + consteval string_view get_format_types() noexcept { + return "aAbBcdeEfFgGopPsxX?"; + } + + template + basic_string get_colons() { + return basic_string(N, CharT(':')); + } + + template + vector> fmt_invalid_types(string_view valid) { + vector> result; + if constexpr (is_permissive_v) { + for (const char c : get_format_types()) { + if (valid.find(c) == string_view::npos) { + result.push_back(format(SV("{{{}{}}}"), get_colons(), c)); + } + } + } else { + // ranges::to is not available in C++20. + ranges::copy(get_format_types() | views::filter([&](char type) { + return valid.find(type) == string_view::npos; + }) | views::transform([&](char type) { return format(SV("{{{}{}}}"), get_colons(), type); }), + back_inserter(result)); + } + return result; + } +} // namespace detail + +// Creates format string for the invalid types. +// +// valid contains a list of types that are valid. +// +// The return value is a collection of basic_strings, instead of +// basic_string_views since the values are temporaries. +template +vector> fmt_invalid_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +// Like fmt_invalid_types but when the format spec is for an underlying formatter. +template +vector> fmt_invalid_nested_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +template +struct context {}; + +template <> +struct context { + using type = format_context; +}; + +template <> +struct context { + using type = wformat_context; +}; + +template +using context_t = context::type; + +// A user-defined type used to test the handle formatter. +enum class status : uint16_t { foo = 0xAAAA, bar = 0x5555, foobar = 0xAA55 }; + +// The formatter for a user-defined type used to test the handle formatter. +template +struct formatter { + // During the 2023 Issaquah meeting LEWG made it clear a formatter is + // required to call its parse function. LWG3892 Adds the wording for that + // requirement. Therefore this formatter is initialized in an invalid state. + // A call to parse sets it in a valid state and a call to format validates + // the state. + int type = -1; + + constexpr auto parse(basic_format_parse_context& parse_ctx) -> decltype(parse_ctx.begin()) { + auto begin = parse_ctx.begin(); + auto end = parse_ctx.end(); + type = 0; + if (begin == end) { + return begin; + } + + switch (*begin) { + case CharT('x'): + break; + case CharT('X'): + type = 1; + break; + case CharT('s'): + type = 2; + break; + case CharT('}'): + return begin; + default: + throw_format_error("The type option contains an invalid value for a status formatting argument"); + } + + ++begin; + if (begin != end && *begin != CharT('}')) { + throw_format_error("The format specifier should consume the input or end with a '}'"); + } + + return begin; + } + + template + auto format(status s, basic_format_context& ctx) const -> decltype(ctx.out()) { + const char* names[] = {"foo", "bar", "foobar"}; + char buffer[7]; + const char* pbegin = names[0]; + const char* pend = names[0]; + switch (type) { + case -1: + throw_format_error("The formatter's parse function has not been called."); + + case 0: + pbegin = buffer; + buffer[0] = '0'; + buffer[1] = 'x'; + pend = to_chars(&buffer[2], end(buffer), static_cast(s), 16).ptr; + buffer[6] = '\0'; + break; + + case 1: + pbegin = buffer; + buffer[0] = '0'; + buffer[1] = 'X'; + pend = to_chars(&buffer[2], end(buffer), static_cast(s), 16).ptr; + transform(static_cast(&buffer[2]), pend, &buffer[2], + [](char c) { return static_cast(toupper(c)); }); + buffer[6] = '\0'; + break; + + case 2: + switch (s) { + case status::foo: + pbegin = names[0]; + break; + case status::bar: + pbegin = names[1]; + break; + case status::foobar: + pbegin = names[2]; + break; + } + pend = pbegin + strlen(pbegin); + break; + } + +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(push) +#pragma warning(disable : 4365) +#endif // !defined(__clang__) && !defined(__EDG__) + return copy(pbegin, pend, ctx.out()); +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(pop) +#endif // !defined(__clang__) && !defined(__EDG__) + } + +private: + [[noreturn]] void throw_format_error(const char* s) const { + throw format_error(s); + } +}; + + +template +class test_cpp20_input_iterator { + It it_; + +public: + using value_type = iter_value_t; + using difference_type = iter_difference_t; + using iterator_concept = input_iterator_tag; + + constexpr explicit test_cpp20_input_iterator(It it) : it_(it) {} + test_cpp20_input_iterator(test_cpp20_input_iterator&&) = default; + test_cpp20_input_iterator& operator=(test_cpp20_input_iterator&&) = default; + constexpr decltype(auto) operator*() const { + return *it_; + } + constexpr test_cpp20_input_iterator& operator++() { + ++it_; + return *this; + } + constexpr void operator++(int) { + ++it_; + } + + friend constexpr It base(const test_cpp20_input_iterator& i) { + return i.it_; + } + + template + void operator,(T const&) = delete; +}; +template +test_cpp20_input_iterator(It) -> test_cpp20_input_iterator; + +static_assert(input_iterator>); + +template +class test_forward_iterator { + It it_; + + template + friend class test_forward_iterator; + +public: + using iterator_category = forward_iterator_tag; + using value_type = iterator_traits::value_type; + using difference_type = iterator_traits::difference_type; + using pointer = It; + using reference = iterator_traits::reference; + + constexpr test_forward_iterator() : it_() {} + constexpr explicit test_forward_iterator(It it) : it_(it) {} + + template + constexpr test_forward_iterator(const test_forward_iterator& u) : it_(u.it_) {} + + template + requires is_default_constructible_v + constexpr test_forward_iterator(test_forward_iterator&& other) : it_(other.it_) { + other.it_ = U(); + } + + constexpr reference operator*() const { + return *it_; + } + + constexpr test_forward_iterator& operator++() { + ++it_; + return *this; + } + constexpr test_forward_iterator operator++(int) { + return test_forward_iterator(it_++); + } + + friend constexpr bool operator==(const test_forward_iterator& x, const test_forward_iterator& y) { + return x.it_ == y.it_; + } + friend constexpr bool operator!=(const test_forward_iterator& x, const test_forward_iterator& y) { + return x.it_ != y.it_; + } + + friend constexpr It base(const test_forward_iterator& i) { + return i.it_; + } + + template + void operator,(T const&) = delete; +}; +template +test_forward_iterator(It) -> test_forward_iterator; + +template +class test_bidirectional_iterator { + It it_; + + template + friend class test_bidirectional_iterator; + +public: + using iterator_category = bidirectional_iterator_tag; + using value_type = iterator_traits::value_type; + using difference_type = iterator_traits::difference_type; + using pointer = It; + using reference = iterator_traits::reference; + + constexpr test_bidirectional_iterator() : it_() {} + constexpr explicit test_bidirectional_iterator(It it) : it_(it) {} + + template + constexpr test_bidirectional_iterator(const test_bidirectional_iterator& u) : it_(u.it_) {} + + template + requires is_default_constructible_v + constexpr test_bidirectional_iterator(test_bidirectional_iterator&& u) : it_(u.it_) { + u.it_ = U(); + } + + constexpr reference operator*() const { + return *it_; + } + + constexpr test_bidirectional_iterator& operator++() { + ++it_; + return *this; + } + constexpr test_bidirectional_iterator& operator--() { + --it_; + return *this; + } + constexpr test_bidirectional_iterator operator++(int) { + return test_bidirectional_iterator(it_++); + } + constexpr test_bidirectional_iterator operator--(int) { + return test_bidirectional_iterator(it_--); + } + + friend constexpr bool operator==(const test_bidirectional_iterator& x, const test_bidirectional_iterator& y) { + return x.it_ == y.it_; + } + friend constexpr bool operator!=(const test_bidirectional_iterator& x, const test_bidirectional_iterator& y) { + return x.it_ != y.it_; + } + + friend constexpr It base(const test_bidirectional_iterator& i) { + return i.it_; + } + + template + void operator,(T const&) = delete; +}; +template +test_bidirectional_iterator(It) -> test_bidirectional_iterator; + +template +class test_random_access_iterator { + It it_; + + template + friend class test_random_access_iterator; + +public: + using iterator_category = random_access_iterator_tag; + using value_type = iterator_traits::value_type; + using difference_type = iterator_traits::difference_type; + using pointer = It; + using reference = iterator_traits::reference; + + constexpr test_random_access_iterator() : it_() {} + constexpr explicit test_random_access_iterator(It it) : it_(it) {} + + template + constexpr test_random_access_iterator(const test_random_access_iterator& u) : it_(u.it_) {} + + template + requires is_default_constructible_v + constexpr test_random_access_iterator(test_random_access_iterator&& u) : it_(u.it_) { + u.it_ = U(); + } + + constexpr reference operator*() const { + return *it_; + } + constexpr reference operator[](difference_type n) const { + return it_[n]; + } + + constexpr test_random_access_iterator& operator++() { + ++it_; + return *this; + } + constexpr test_random_access_iterator& operator--() { + --it_; + return *this; + } + constexpr test_random_access_iterator operator++(int) { + return test_random_access_iterator(it_++); + } + constexpr test_random_access_iterator operator--(int) { + return test_random_access_iterator(it_--); + } + + constexpr test_random_access_iterator& operator+=(difference_type n) { + it_ += n; + return *this; + } + constexpr test_random_access_iterator& operator-=(difference_type n) { + it_ -= n; + return *this; + } + friend constexpr test_random_access_iterator operator+(test_random_access_iterator x, difference_type n) { + x += n; + return x; + } + friend constexpr test_random_access_iterator operator+(difference_type n, test_random_access_iterator x) { + x += n; + return x; + } + friend constexpr test_random_access_iterator operator-(test_random_access_iterator x, difference_type n) { + x -= n; + return x; + } + friend constexpr difference_type operator-(test_random_access_iterator x, test_random_access_iterator y) { + return x.it_ - y.it_; + } + + friend constexpr bool operator==(const test_random_access_iterator& x, const test_random_access_iterator& y) { + return x.it_ == y.it_; + } + friend constexpr bool operator!=(const test_random_access_iterator& x, const test_random_access_iterator& y) { + return x.it_ != y.it_; + } + friend constexpr bool operator<(const test_random_access_iterator& x, const test_random_access_iterator& y) { + return x.it_ < y.it_; + } + friend constexpr bool operator<=(const test_random_access_iterator& x, const test_random_access_iterator& y) { + return x.it_ <= y.it_; + } + friend constexpr bool operator>(const test_random_access_iterator& x, const test_random_access_iterator& y) { + return x.it_ > y.it_; + } + friend constexpr bool operator>=(const test_random_access_iterator& x, const test_random_access_iterator& y) { + return x.it_ >= y.it_; + } + + friend constexpr It base(const test_random_access_iterator& i) { + return i.it_; + } + + template + void operator,(T const&) = delete; +}; +template +test_random_access_iterator(It) -> test_random_access_iterator; + +template +class test_contiguous_iterator { + static_assert(is_pointer_v, "Things probably break in this case"); + + It it_; + + template + friend class test_contiguous_iterator; + +public: + using iterator_category = contiguous_iterator_tag; + using value_type = iterator_traits::value_type; + using difference_type = iterator_traits::difference_type; + using pointer = It; + using reference = iterator_traits::reference; + using element_type = remove_pointer_t; + + constexpr It base() const { + return it_; + } + + constexpr test_contiguous_iterator() : it_() {} + constexpr explicit test_contiguous_iterator(It it) : it_(it) {} + + template + constexpr test_contiguous_iterator(const test_contiguous_iterator& u) : it_(u.it_) {} + + template + requires is_default_constructible_v + constexpr test_contiguous_iterator(test_contiguous_iterator&& u) : it_(u.it_) { + u.it_ = U(); + } + + constexpr reference operator*() const { + return *it_; + } + constexpr pointer operator->() const { + return it_; + } + constexpr reference operator[](difference_type n) const { + return it_[n]; + } + + constexpr test_contiguous_iterator& operator++() { + ++it_; + return *this; + } + constexpr test_contiguous_iterator& operator--() { + --it_; + return *this; + } + constexpr test_contiguous_iterator operator++(int) { + return test_contiguous_iterator(it_++); + } + constexpr test_contiguous_iterator operator--(int) { + return test_contiguous_iterator(it_--); + } + + constexpr test_contiguous_iterator& operator+=(difference_type n) { + it_ += n; + return *this; + } + constexpr test_contiguous_iterator& operator-=(difference_type n) { + it_ -= n; + return *this; + } + friend constexpr test_contiguous_iterator operator+(test_contiguous_iterator x, difference_type n) { + x += n; + return x; + } + friend constexpr test_contiguous_iterator operator+(difference_type n, test_contiguous_iterator x) { + x += n; + return x; + } + friend constexpr test_contiguous_iterator operator-(test_contiguous_iterator x, difference_type n) { + x -= n; + return x; + } + friend constexpr difference_type operator-(test_contiguous_iterator x, test_contiguous_iterator y) { + return x.it_ - y.it_; + } + + friend constexpr bool operator==(const test_contiguous_iterator& x, const test_contiguous_iterator& y) { + return x.it_ == y.it_; + } + friend constexpr bool operator!=(const test_contiguous_iterator& x, const test_contiguous_iterator& y) { + return x.it_ != y.it_; + } + friend constexpr bool operator<(const test_contiguous_iterator& x, const test_contiguous_iterator& y) { + return x.it_ < y.it_; + } + friend constexpr bool operator<=(const test_contiguous_iterator& x, const test_contiguous_iterator& y) { + return x.it_ <= y.it_; + } + friend constexpr bool operator>(const test_contiguous_iterator& x, const test_contiguous_iterator& y) { + return x.it_ > y.it_; + } + friend constexpr bool operator>=(const test_contiguous_iterator& x, const test_contiguous_iterator& y) { + return x.it_ >= y.it_; + } + + friend constexpr It base(const test_contiguous_iterator& i) { + return i.it_; + } + + template + void operator,(T const&) = delete; +}; +template +test_contiguous_iterator(It) -> test_contiguous_iterator; + + +// +// Char +// + +template +void test_char_default(TestFunction check, ExceptionTest check_exception, auto&& input) { + // Note when no range-underlying-spec is present the char is escaped, + check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{}"), input); + check(SV("['H', 'e', 'l', 'l', 'o']^42"), SV("{}^42"), input); + check(SV("['H', 'e', 'l', 'l', 'o']^42"), SV("{:}^42"), input); + + // when one is present there is no escaping, + check(SV("[H, e, l, l, o]"), SV("{::}"), input); + check(SV("[H, e, l, l, o]"), SV("{::<}"), input); + // unless forced by the type specifier. + check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{::?}"), input); + check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{::30}"), input); + + check(SV("['H', 'e', 'l', 'l', 'o'] "), SV("{:{}}"), input, 30); + check(SV("['H', 'e', 'l', 'l', 'o']*****"), SV("{:*<{}}"), input, 30); + check(SV("__['H', 'e', 'l', 'l', 'o']___"), SV("{:_^{}}"), input, 30); + check(SV("#####['H', 'e', 'l', 'l', 'o']"), SV("{:#>{}}"), input, 30); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__'H', 'e', 'l', 'l', 'o'___"), SV("{:_^28n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[H , e , l , l , o ]"), SV("{::4}"), input); + check(SV("[H***, e***, l***, l***, o***]"), SV("{::*<4}"), input); + check(SV("[_H__, _e__, _l__, _l__, _o__]"), SV("{::_^4}"), input); + check(SV("[:::H, :::e, :::l, :::l, :::o]"), SV("{:::>4}"), input); + + check(SV("[H , e , l , l , o ]"), SV("{::{}}"), input, 4); + check(SV("[H***, e***, l***, l***, o***]"), SV("{::*<{}}"), input, 4); + check(SV("[_H__, _e__, _l__, _l__, _o__]"), SV("{::_^{}}"), input, 4); + check(SV("[:::H, :::e, :::l, :::l, :::o]"), SV("{:::>{}}"), input, 4); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier for a character does not allow the sign option", SV("{::-}"), input); + check_exception("The format specifier for a character does not allow the sign option", SV("{::+}"), input); + check_exception("The format specifier for a character does not allow the sign option", SV("{:: }"), input); + + check(SV("[72, 101, 108, 108, 111]"), SV("{::-d}"), input); + check(SV("[+72, +101, +108, +108, +111]"), SV("{::+d}"), input); + check(SV("[ 72, 101, 108, 108, 111]"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception( + "The format specifier for a character does not allow the alternate form option", SV("{::#}"), input); + + check(SV("[0x48, 0x65, 0x6c, 0x6c, 0x6f]"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("The format specifier for a character does not allow the zero-padding option", SV("{::05}"), input); + + check(SV("[00110, 00145, 00154, 00154, 00157]"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[H, e, l, l, o]"), SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBcdoxX?")) { + check_exception("The type option contains an invalid value for a character formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^25::>2}"), input); + check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^{}::>2}"), input, 25); + check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^{}::>{}}"), input, 25, 2); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>2}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 25); +} + +template +void test_char_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("Hello"), SV("{:s}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("Hello "), SV("{:8s}"), input); + check(SV("Hello***"), SV("{:*<8s}"), input); + check(SV("_Hello__"), SV("{:_^8s}"), input); + check(SV("###Hello"), SV("{:#>8s}"), input); + + check(SV("Hello "), SV("{:{}s}"), input, 8); + check(SV("Hello***"), SV("{:*<{}s}"), input, 8); + check(SV("_Hello__"), SV("{:_^{}s}"), input, 8); + check(SV("###Hello"), SV("{:#>{}s}"), input, 8); + + check_exception("The format string contains an invalid escape sequence", SV("{:} fmt : fmt_invalid_nested_types("bBcdoxX?")) { + check_exception("The type option contains an invalid value for a character formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check_exception("Type s and an underlying format specification can't be used together", SV("{:5s:5}"), input); +} + +template +void test_char_escaped_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV(R"("\"Hello'")"), SV("{:?s}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"("\"Hello'" )"), SV("{:13?s}"), input); + check(SV(R"("\"Hello'"***)"), SV("{:*<13?s}"), input); + check(SV(R"(_"\"Hello'"__)"), SV("{:_^13?s}"), input); + check(SV(R"(###"\"Hello'")"), SV("{:#>13?s}"), input); + + check(SV(R"("\"Hello'" )"), SV("{:{}?s}"), input, 13); + check(SV(R"("\"Hello'"***)"), SV("{:*<{}?s}"), input, 13); + check(SV(R"(_"\"Hello'"__)"), SV("{:_^{}?s}"), input, 13); + check(SV(R"(###"\"Hello'")"), SV("{:#>{}?s}"), input, 13); + + check_exception("The format string contains an invalid escape sequence", SV("{:} +void test_char(TestFunction check, ExceptionTest check_exception) { + test_char_default(check, check_exception, array{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}); + + // This tests two different implementations in libc++. A basic_string_view + // formatter if the range is contiguous, a basic_string otherwise. + test_char_escaped_string(check, check_exception, + array{CharT('"'), CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o'), CharT('\'')}); + test_char_escaped_string(check, check_exception, + list{CharT('"'), CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o'), CharT('\'')}); + + // This tests two different implementations in libc++. A basic_string_view + // formatter if the range is contiguous, a basic_string otherwise. + test_char_string(check, check_exception, array{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}); + test_char_string(check, check_exception, list{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}); +} + +// +// char -> wchar_t +// + +template +void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { + test_char_default(check, check_exception, array{'H', 'e', 'l', 'l', 'o'}); + + // The types s and ?s may only be used when using range_formatter + // where the types T and charT are the same. This means this can't be used for + // range_formatter even when formatter has a + // debug-enabled specialization. + + using CharT = wchar_t; + check_exception( + "Type s requires character type as formatting argument", SV("{:s}"), array{'H', 'e', 'l', 'l', 'o'}); + check_exception( + "Type ?s requires character type as formatting argument", SV("{:?s}"), array{'H', 'e', 'l', 'l', 'o'}); +} + +// +// Bool +// + +template +void test_bool(TestFunction check, ExceptionTest check_exception) { + array input{true, true, false}; + + check(SV("[true, true, false]"), SV("{}"), input); + check(SV("[true, true, false]^42"), SV("{}^42"), input); + check(SV("[true, true, false]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[true, true, false] "), SV("{:24}"), input); + check(SV("[true, true, false]*****"), SV("{:*<24}"), input); + check(SV("__[true, true, false]___"), SV("{:_^24}"), input); + check(SV("#####[true, true, false]"), SV("{:#>24}"), input); + + check(SV("[true, true, false] "), SV("{:{}}"), input, 24); + check(SV("[true, true, false]*****"), SV("{:*<{}}"), input, 24); + check(SV("__[true, true, false]___"), SV("{:_^{}}"), input, 24); + check(SV("#####[true, true, false]"), SV("{:#>{}}"), input, 24); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__true, true, false___"), SV("{:_^22n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[true , true , false ]"), SV("{::7}"), input); + check(SV("[true***, true***, false**]"), SV("{::*<7}"), input); + check(SV("[_true__, _true__, _false_]"), SV("{::_^7}"), input); + check(SV("[:::true, :::true, ::false]"), SV("{:::>7}"), input); + + check(SV("[true , true , false ]"), SV("{::{}}"), input, 7); + check(SV("[true***, true***, false**]"), SV("{::*<{}}"), input, 7); + check(SV("[_true__, _true__, _false_]"), SV("{::_^{}}"), input, 7); + check(SV("[:::true, :::true, ::false]"), SV("{:::>{}}"), input, 7); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier for a bool does not allow the sign option", SV("{::-}"), input); + check_exception("The format specifier for a bool does not allow the sign option", SV("{::+}"), input); + check_exception("The format specifier for a bool does not allow the sign option", SV("{:: }"), input); + + check(SV("[1, 1, 0]"), SV("{::-d}"), input); + check(SV("[+1, +1, +0]"), SV("{::+d}"), input); + check(SV("[ 1, 1, 0]"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception("The format specifier for a bool does not allow the alternate form option", SV("{::#}"), input); + + check(SV("[0x1, 0x1, 0x0]"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("The format specifier for a bool does not allow the zero-padding option", SV("{::05}"), input); + + check(SV("[00001, 00001, 00000]"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[true, true, false]"), SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBdosxX")) { + check_exception("The type option contains an invalid value for a bool formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^32::>7}"), input); + check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^{}::>7}"), input, 32); + check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^{}::>{}}"), input, 32, 7); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 32); +} + +// +// Integral +// + +template +void test_int(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[1, 2, 42, -42]"), SV("{}"), input); + check(SV("[1, 2, 42, -42]^42"), SV("{}^42"), input); + check(SV("[1, 2, 42, -42]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[1, 2, 42, -42] "), SV("{:20}"), input); + check(SV("[1, 2, 42, -42]*****"), SV("{:*<20}"), input); + check(SV("__[1, 2, 42, -42]___"), SV("{:_^20}"), input); + check(SV("#####[1, 2, 42, -42]"), SV("{:#>20}"), input); + + check(SV("[1, 2, 42, -42] "), SV("{:{}}"), input, 20); + check(SV("[1, 2, 42, -42]*****"), SV("{:*<{}}"), input, 20); + check(SV("__[1, 2, 42, -42]___"), SV("{:_^{}}"), input, 20); + check(SV("#####[1, 2, 42, -42]"), SV("{:#>{}}"), input, 20); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__1, 2, 42, -42___"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[ 1, 2, 42, -42]"), SV("{::5}"), input); + check(SV("[1****, 2****, 42***, -42**]"), SV("{::*<5}"), input); + check(SV("[__1__, __2__, _42__, _-42_]"), SV("{::_^5}"), input); + check(SV("[::::1, ::::2, :::42, ::-42]"), SV("{:::>5}"), input); + + check(SV("[ 1, 2, 42, -42]"), SV("{::{}}"), input, 5); + check(SV("[1****, 2****, 42***, -42**]"), SV("{::*<{}}"), input, 5); + check(SV("[__1__, __2__, _42__, _-42_]"), SV("{::_^{}}"), input, 5); + check(SV("[::::1, ::::2, :::42, ::-42]"), SV("{:::>{}}"), input, 5); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check(SV("[1, 2, 42, -42]"), SV("{::-}"), input); + check(SV("[+1, +2, +42, -42]"), SV("{::+}"), input); + check(SV("[ 1, 2, 42, -42]"), SV("{:: }"), input); + + // *** alternate form *** + check(SV("[0x1, 0x2, 0x2a, -0x2a]"), SV("{::#x}"), input); + + // *** zero-padding *** + check(SV("[00001, 00002, 00042, -0042]"), SV("{::05}"), input); + check(SV("[00001, 00002, 0002a, -002a]"), SV("{::05x}"), input); + check(SV("[0x001, 0x002, 0x02a, -0x2a]"), SV("{::#05x}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[1, 2, 42, -42]"), SV("{::L}"), input); // does nothing in this test, but is accepted. + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBcdoxX")) { + check_exception("The type option contains an invalid value for an integer formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[::::1, ::::2, :::42, ::-42]^^^"), SV("{:^^33::>5}"), input); + check(SV("^^[::::1, ::::2, :::42, ::-42]^^^"), SV("{:^^{}::>5}"), input, 33); + check(SV("^^[::::1, ::::2, :::42, ::-42]^^^"), SV("{:^^{}::>{}}"), input, 33, 5); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 33); +} + +template +void test_int(TestFunction check, ExceptionTest check_exception) { + test_int(check, check_exception, array{1, 2, 42, -42}); + test_int(check, check_exception, list{1, 2, 42, -42}); + test_int(check, check_exception, vector{1, 2, 42, -42}); + array input{1, 2, 42, -42}; + test_int(check, check_exception, span{input}); +} + +// +// Floating point +// + +template +void test_floating_point(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{}"), input); + check(SV("[-42.5, 0, 1.25, 42.5]^42"), SV("{}^42"), input); + check(SV("[-42.5, 0, 1.25, 42.5]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[-42.5, 0, 1.25, 42.5] "), SV("{:27}"), input); + check(SV("[-42.5, 0, 1.25, 42.5]*****"), SV("{:*<27}"), input); + check(SV("__[-42.5, 0, 1.25, 42.5]___"), SV("{:_^27}"), input); + check(SV("#####[-42.5, 0, 1.25, 42.5]"), SV("{:#>27}"), input); + + check(SV("[-42.5, 0, 1.25, 42.5] "), SV("{:{}}"), input, 27); + check(SV("[-42.5, 0, 1.25, 42.5]*****"), SV("{:*<{}}"), input, 27); + check(SV("__[-42.5, 0, 1.25, 42.5]___"), SV("{:_^{}}"), input, 27); + check(SV("#####[-42.5, 0, 1.25, 42.5]"), SV("{:#>{}}"), input, 27); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__-42.5, 0, 1.25, 42.5___"), SV("{:_^25n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::5}"), input); + check(SV("[-42.5, 0****, 1.25*, 42.5*]"), SV("{::*<5}"), input); + check(SV("[-42.5, __0__, 1.25_, 42.5_]"), SV("{::_^5}"), input); + check(SV("[-42.5, ::::0, :1.25, :42.5]"), SV("{:::>5}"), input); + + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::{}}"), input, 5); + check(SV("[-42.5, 0****, 1.25*, 42.5*]"), SV("{::*<{}}"), input, 5); + check(SV("[-42.5, __0__, 1.25_, 42.5_]"), SV("{::_^{}}"), input, 5); + check(SV("[-42.5, ::::0, :1.25, :42.5]"), SV("{:::>{}}"), input, 5); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::-}"), input); + check(SV("[-42.5, +0, +1.25, +42.5]"), SV("{::+}"), input); + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{:: }"), input); + + // *** alternate form *** + check(SV("[-42.5, 0., 1.25, 42.5]"), SV("{::#}"), input); + + // *** zero-padding *** + check(SV("[-42.5, 00000, 01.25, 042.5]"), SV("{::05}"), input); + check(SV("[-42.5, 0000., 01.25, 042.5]"), SV("{::#05}"), input); + + // *** precision *** + check(SV("[-42, 0, 1.2, 42]"), SV("{::.2}"), input); + check(SV("[-42.500, 0.000, 1.250, 42.500]"), SV("{::.3f}"), input); + + check(SV("[-42, 0, 1.2, 42]"), SV("{::.{}}"), input, 2); + check(SV("[-42.500, 0.000, 1.250, 42.500]"), SV("{::.{}f}"), input, 3); + + check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input); + +#if !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + // *** locale-specific form *** + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::L}"), input); // does not require locales present + + locale::global(locale("fr-FR")); + check(SV("[-42,5, 0, 1,25, 42,5]"), SV("{::L}"), input); + + locale::global(locale("en-US")); + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::L}"), input); + + locale::global(locale::classic()); +#endif // !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("aAeEfFgG")) { + check_exception( + "The type option contains an invalid value for a floating-point formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^33::>5}"), input); + check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^{}::>5}"), input, 33); + check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^{}::>{}}"), input, 33, 5); + + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^33::>5.2}"), input); + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>5.2}"), input, 33); + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>{}.2}"), input, 33, 5); + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>{}.{}}"), input, 33, 5, 2); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5.2}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}.2}"), input, 33); + check_exception("The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}.{}}"), + input, 33, 5); +} + +template +void test_floating_point(TestFunction check, ExceptionTest check_exception) { + test_floating_point(check, check_exception, array{-42.5f, 0.0f, 1.25f, 42.5f}); + test_floating_point(check, check_exception, vector{-42.5, 0.0, 1.25, 42.5}); + + array input{-42.5l, 0.0l, 1.25l, 42.5l}; + test_floating_point(check, check_exception, span{input}); +} + +// +// Pointer +// + +template +void test_pointer(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[0x0]"), SV("{}"), input); + check(SV("[0x0]^42"), SV("{}^42"), input); + check(SV("[0x0]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[0x0] "), SV("{:10}"), input); + check(SV("[0x0]*****"), SV("{:*<10}"), input); + check(SV("__[0x0]___"), SV("{:_^10}"), input); + check(SV("#####[0x0]"), SV("{:#>10}"), input); + + check(SV("[0x0] "), SV("{:{}}"), input, 10); + check(SV("[0x0]*****"), SV("{:*<{}}"), input, 10); + check(SV("__[0x0]___"), SV("{:_^{}}"), input, 10); + check(SV("#####[0x0]"), SV("{:#>{}}"), input, 10); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("_0x0_"), SV("{:_^5n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[ 0x0]"), SV("{::5}"), input); + check(SV("[0x0**]"), SV("{::*<5}"), input); + check(SV("[_0x0_]"), SV("{::_^5}"), input); + check(SV("[::0x0]"), SV("{:::>5}"), input); + + check(SV("[ 0x0]"), SV("{::{}}"), input, 5); + check(SV("[0x0**]"), SV("{::*<{}}"), input, 5); + check(SV("[_0x0_]"), SV("{::_^{}}"), input, 5); + check(SV("[::0x0]"), SV("{:::>{}}"), input, 5); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check(SV("[0x0000]"), SV("{::06}"), input); + check(SV("[0x0000]"), SV("{::06p}"), input); + check(SV("[0X0000]"), SV("{::06P}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("pP")) { + check_exception("The type option contains an invalid value for a pointer formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[::0x0]^^^"), SV("{:^^12::>5}"), input); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>5}"), input, 12); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>{}}"), input, 12, 5); + + check(SV("^^[::0x0]^^^"), SV("{:^^12::>5}"), input); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>5}"), input, 12); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>{}}"), input, 12, 5); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 12); +} + +template +void test_pointer(TestFunction check, ExceptionTest check_exception) { + test_pointer(check, check_exception, array{nullptr}); + test_pointer(check, check_exception, array{static_cast(0)}); + test_pointer(check, check_exception, array{static_cast(0)}); +} + +// +// String +// + +template +void test_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV(R"(["Hello", "world"])"), SV("{}"), input); + check(SV(R"(["Hello", "world"]^42)"), SV("{}^42"), input); + check(SV(R"(["Hello", "world"]^42)"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"(["Hello", "world"] )"), SV("{:23}"), input); + check(SV(R"(["Hello", "world"]*****)"), SV("{:*<23}"), input); + check(SV(R"(__["Hello", "world"]___)"), SV("{:_^23}"), input); + check(SV(R"(#####["Hello", "world"])"), SV("{:#>23}"), input); + + check(SV(R"(["Hello", "world"] )"), SV("{:{}}"), input, 23); + check(SV(R"(["Hello", "world"]*****)"), SV("{:*<{}}"), input, 23); + check(SV(R"(__["Hello", "world"]___)"), SV("{:_^{}}"), input, 23); + check(SV(R"(#####["Hello", "world"])"), SV("{:#>{}}"), input, 23); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV(R"(_"Hello", "world"_)"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV(R"([Hello , world ])"), SV("{::8}"), input); + check(SV(R"([Hello***, world***])"), SV("{::*<8}"), input); + check(SV(R"([_Hello__, _world__])"), SV("{::_^8}"), input); + check(SV(R"([:::Hello, :::world])"), SV("{:::>8}"), input); + + check(SV(R"([Hello , world ])"), SV("{::{}}"), input, 8); + check(SV(R"([Hello***, world***])"), SV("{::*<{}}"), input, 8); + check(SV(R"([_Hello__, _world__])"), SV("{::_^{}}"), input, 8); + check(SV(R"([:::Hello, :::world])"), SV("{:::>{}}"), input, 8); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check(SV(R"([Hel, wor])"), SV("{::.3}"), input); + + check(SV(R"([Hel, wor])"), SV("{::.{}}"), input, 3); + + check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("s?")) { + check_exception("The type option contains an invalid value for a string formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>8}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 25); +} + +template +void test_string(TestFunction check, ExceptionTest check_exception) { + test_string(check, check_exception, array{CSTR("Hello"), CSTR("world")}); + test_string(check, check_exception, array{STR("Hello"), STR("world")}); + test_string(check, check_exception, array{SV("Hello"), SV("world")}); +} + +// +// Handle +// + +template +void test_status(TestFunction check, ExceptionTest check_exception) { + array input{status::foo, status::bar, status::foobar}; + + check(SV("[0xaaaa, 0x5555, 0xaa55]"), SV("{}"), input); + check(SV("[0xaaaa, 0x5555, 0xaa55]^42"), SV("{}^42"), input); + check(SV("[0xaaaa, 0x5555, 0xaa55]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[0xaaaa, 0x5555, 0xaa55] "), SV("{:29}"), input); + check(SV("[0xaaaa, 0x5555, 0xaa55]*****"), SV("{:*<29}"), input); + check(SV("__[0xaaaa, 0x5555, 0xaa55]___"), SV("{:_^29}"), input); + check(SV("#####[0xaaaa, 0x5555, 0xaa55]"), SV("{:#>29}"), input); + + check(SV("[0xaaaa, 0x5555, 0xaa55] "), SV("{:{}}"), input, 29); + check(SV("[0xaaaa, 0x5555, 0xaa55]*****"), SV("{:*<{}}"), input, 29); + check(SV("__[0xaaaa, 0x5555, 0xaa55]___"), SV("{:_^{}}"), input, 29); + check(SV("#####[0xaaaa, 0x5555, 0xaa55]"), SV("{:#>{}}"), input, 29); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__0xaaaa, 0x5555, 0xaa55___"), SV("{:_^27n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check_exception("The type option contains an invalid value for a status formatting argument", SV("{::*<7}"), input); + for (basic_string_view fmt : fmt_invalid_nested_types("sxX")) { + check_exception("The type option contains an invalid value for a status formatting argument", fmt, input); + } + + check(SV("[0xaaaa, 0x5555, 0xaa55]"), SV("{::x}"), input); + check(SV("[0XAAAA, 0X5555, 0XAA55]"), SV("{::X}"), input); + check(SV("[foo, bar, foobar]"), SV("{::s}"), input); + + // ***** Both have a format-spec + check(SV("^^[0XAAAA, 0X5555, 0XAA55]^^^"), SV("{:^^29:X}"), input); + check(SV("^^[0XAAAA, 0X5555, 0XAA55]^^^"), SV("{:^^{}:X}"), input, 29); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:X}"), input); +} + +// +// Pair +// + +template +void test_pair_tuple(TestFunction check, ExceptionTest check_exception, auto&& input) { + // [format.range.formatter]/3 + // For range_formatter, the format-spec in a + // range-underlying-spec, if any, is interpreted by formatter. + // + // template + // constexpr typename ParseContext::iterator + // parse(ParseContext& ctx); + // [format.tuple]/7 + // ... if e.set_debug_format() is a valid expression, calls + // e.set_debug_format(). + // So when there is no range-underlying-spec, there is no need to call parse + // thus the char element is not escaped. + // TODO FMT P2733 addresses this issue. + check(SV("[(1, 'a'), (42, '*')]"), SV("{}"), input); + check(SV("[(1, 'a'), (42, '*')]^42"), SV("{}^42"), input); + check(SV("[(1, 'a'), (42, '*')]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[(1, 'a'), (42, '*')] "), SV("{:26}"), input); + check(SV("[(1, 'a'), (42, '*')]*****"), SV("{:*<26}"), input); + check(SV("__[(1, 'a'), (42, '*')]___"), SV("{:_^26}"), input); + check(SV("#####[(1, 'a'), (42, '*')]"), SV("{:#>26}"), input); + + check(SV("[(1, 'a'), (42, '*')] "), SV("{:{}}"), input, 26); + check(SV("[(1, 'a'), (42, '*')]*****"), SV("{:*<{}}"), input, 26); + check(SV("__[(1, 'a'), (42, '*')]___"), SV("{:_^{}}"), input, 26); + check(SV("#####[(1, 'a'), (42, '*')]"), SV("{:#>{}}"), input, 26); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__(1, 'a'), (42, '*')___"), SV("{:_^24n}"), input); + check(SV("____1: 'a', 42: '*'_____"), SV("{:_^24nm}"), input); + + // *** type *** + check(SV("____{1: 'a', 42: '*'}_____"), SV("{:_^26m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[(1, 'a') , (42, '*') ]"), SV("{::11}"), input); + check(SV("[(1, 'a')***, (42, '*')**]"), SV("{::*<11}"), input); + check(SV("[_(1, 'a')__, _(42, '*')_]"), SV("{::_^11}"), input); + check(SV("[###(1, 'a'), ##(42, '*')]"), SV("{::#>11}"), input); + + check(SV("[(1, 'a') , (42, '*') ]"), SV("{::{}}"), input, 11); + check(SV("[(1, 'a')***, (42, '*')**]"), SV("{::*<{}}"), input, 11); + check(SV("[_(1, 'a')__, _(42, '*')_]"), SV("{::_^{}}"), input, 11); + check(SV("[###(1, 'a'), ##(42, '*')]"), SV("{::#>{}}"), input, 11); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + check(SV("[1: 'a', 42: '*']"), SV("{::m}"), input); + check(SV("[1, 'a', 42, '*']"), SV("{::n}"), input); + for (basic_string_view fmt : fmt_invalid_nested_types("")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^31:#>11}"), input); + check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^31:#>11}"), input); + check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^{}:#>11}"), input, 31); + check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^{}:#>{}}"), input, 31, 11); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 31); + + check(SV("1: 'a', 42: '*'"), SV("{:n:m}"), input); + check(SV("1, 'a', 42, '*'"), SV("{:n:n}"), input); + check(SV("{1: 'a', 42: '*'}"), SV("{:m:m}"), input); + check(SV("{1: 'a', 42: '*'}"), SV("{:m:n}"), input); +} + +template +void test_pair_tuple(TestFunction check, ExceptionTest check_exception) { + test_pair_tuple(check, check_exception, array{make_pair(1, CharT('a')), make_pair(42, CharT('*'))}); + test_pair_tuple(check, check_exception, array{make_tuple(1, CharT('a')), make_tuple(42, CharT('*'))}); +} + +// +// Tuple 1 +// + +template +void test_tuple_int(TestFunction check, ExceptionTest check_exception) { + array input{make_tuple(42), make_tuple(99)}; + + check(SV("[(42), (99)]"), SV("{}"), input); + check(SV("[(42), (99)]^42"), SV("{}^42"), input); + check(SV("[(42), (99)]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[(42), (99)] "), SV("{:17}"), input); + check(SV("[(42), (99)]*****"), SV("{:*<17}"), input); + check(SV("__[(42), (99)]___"), SV("{:_^17}"), input); + check(SV("#####[(42), (99)]"), SV("{:#>17}"), input); + + check(SV("[(42), (99)] "), SV("{:{}}"), input, 17); + check(SV("[(42), (99)]*****"), SV("{:*<{}}"), input, 17); + check(SV("__[(42), (99)]___"), SV("{:_^{}}"), input, 17); + check(SV("#####[(42), (99)]"), SV("{:#>{}}"), input, 17); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__(42), (99)___"), SV("{:_^15n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[(42) , (99) ]"), SV("{::7}"), input); + check(SV("[(42)***, (99)***]"), SV("{::*<7}"), input); + check(SV("[_(42)__, _(99)__]"), SV("{::_^7}"), input); + check(SV("[###(42), ###(99)]"), SV("{::#>7}"), input); + + check(SV("[(42) , (99) ]"), SV("{::{}}"), input, 7); + check(SV("[(42)***, (99)***]"), SV("{::*<{}}"), input, 7); + check(SV("[_(42)__, _(99)__]"), SV("{::_^{}}"), input, 7); + check(SV("[###(42), ###(99)]"), SV("{::#>{}}"), input, 7); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + check(SV("[42, 99]"), SV("{::n}"), input); + for (basic_string_view fmt : fmt_invalid_nested_types("")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^23:#>7}"), input); + check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^23:#>7}"), input); + check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^{}:#>7}"), input, 23); + check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^{}:#>{}}"), input, 23, 7); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 23); +} + +// +// Tuple 3 +// + +template +void test_tuple_int_int_int(TestFunction check, ExceptionTest check_exception) { + array input{make_tuple(42, 99, 0), make_tuple(1, 10, 100)}; + + check(SV("[(42, 99, 0), (1, 10, 100)]"), SV("{}"), input); + check(SV("[(42, 99, 0), (1, 10, 100)]^42"), SV("{}^42"), input); + check(SV("[(42, 99, 0), (1, 10, 100)]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[(42, 99, 0), (1, 10, 100)] "), SV("{:32}"), input); + check(SV("[(42, 99, 0), (1, 10, 100)]*****"), SV("{:*<32}"), input); + check(SV("__[(42, 99, 0), (1, 10, 100)]___"), SV("{:_^32}"), input); + check(SV("#####[(42, 99, 0), (1, 10, 100)]"), SV("{:#>32}"), input); + + check(SV("[(42, 99, 0), (1, 10, 100)] "), SV("{:{}}"), input, 32); + check(SV("[(42, 99, 0), (1, 10, 100)]*****"), SV("{:*<{}}"), input, 32); + check(SV("__[(42, 99, 0), (1, 10, 100)]___"), SV("{:_^{}}"), input, 32); + check(SV("#####[(42, 99, 0), (1, 10, 100)]"), SV("{:#>{}}"), input, 32); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__(42, 99, 0), (1, 10, 100)___"), SV("{:_^30n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[(42, 99, 0) , (1, 10, 100) ]"), SV("{::14}"), input); + check(SV("[(42, 99, 0)***, (1, 10, 100)**]"), SV("{::*<14}"), input); + check(SV("[_(42, 99, 0)__, _(1, 10, 100)_]"), SV("{::_^14}"), input); + check(SV("[###(42, 99, 0), ##(1, 10, 100)]"), SV("{::#>14}"), input); + + check(SV("[(42, 99, 0) , (1, 10, 100) ]"), SV("{::{}}"), input, 14); + check(SV("[(42, 99, 0)***, (1, 10, 100)**]"), SV("{::*<{}}"), input, 14); + check(SV("[_(42, 99, 0)__, _(1, 10, 100)_]"), SV("{::_^{}}"), input, 14); + check(SV("[###(42, 99, 0), ##(1, 10, 100)]"), SV("{::#>{}}"), input, 14); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + check(SV("[42, 99, 0, 1, 10, 100]"), SV("{::n}"), input); + for (basic_string_view fmt : fmt_invalid_nested_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^37:#>14}"), input); + check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^37:#>14}"), input); + check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^{}:#>14}"), input, 37); + check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^{}:#>{}}"), input, 37, 14); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 37); +} + +// +// Ranges +// + +template +void test_with_ranges(TestFunction check, ExceptionTest check_exception, auto&& iter) { + ranges::subrange range{move(iter), default_sentinel}; + test_int(check, check_exception, move(range)); +} + +template +void test_with_ranges(TestFunction check, ExceptionTest check_exception) { + array input{1, 2, 42, -42}; + test_with_ranges(check, check_exception, + counted_iterator{test_cpp20_input_iterator(input.data()), static_cast(input.size())}); + test_with_ranges(check, check_exception, + counted_iterator{test_forward_iterator(input.data()), static_cast(input.size())}); + test_with_ranges(check, check_exception, + counted_iterator{test_bidirectional_iterator(input.data()), static_cast(input.size())}); + test_with_ranges(check, check_exception, + counted_iterator{test_random_access_iterator(input.data()), static_cast(input.size())}); + test_with_ranges(check, check_exception, + counted_iterator{test_contiguous_iterator(input.data()), static_cast(input.size())}); +} + +// +// Adaptor +// + +template +class non_contiguous { + // A deque iterator is random access, but not contiguous. + using adaptee = deque; + +public: + using iterator = adaptee::iterator; + using pointer = adaptee::pointer; + + iterator begin() { + return data_.begin(); + } + iterator end() { + return data_.end(); + } + + explicit non_contiguous(adaptee&& data) : data_(move(data)) {} + +private: + adaptee data_; +}; + +template +class contiguous { + // A vector iterator is contiguous. + using adaptee = vector; + +public: + using iterator = adaptee::iterator; + using pointer = adaptee::pointer; + + iterator begin() { + return data_.begin(); + } + iterator end() { + return data_.end(); + } + + explicit contiguous(adaptee&& data) : data_(move(data)) {} + +private: + adaptee data_; +}; + +// This tests two different implementations in libc++. A basic_string_view +// formatter if the range is contiguous, a basic_string otherwise. +template +void test_adaptor(TestFunction check, ExceptionTest check_exception) { + static_assert(format_kind> == range_format::sequence); + static_assert(ranges::sized_range>); + static_assert(!ranges::contiguous_range>); + test_char_string(check, check_exception, + non_contiguous{deque{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}}); + + static_assert(format_kind> == range_format::sequence); + static_assert(ranges::sized_range>); + static_assert(ranges::contiguous_range>); + test_char_string( + check, check_exception, contiguous{vector{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}}); +} + +// +// Driver +// + +template +void format_tests(TestFunction check, ExceptionTest check_exception) { + test_char(check, check_exception); + if (same_as) { // avoid testing twice + test_char_to_wchar(check, check_exception); + } + test_bool(check, check_exception); + test_int(check, check_exception); + test_floating_point(check, check_exception); + test_pointer(check, check_exception); + test_string(check, check_exception); + + test_status(check, check_exception); // Has its own handler with its own parser + + test_pair_tuple(check, check_exception); + test_tuple_int(check, check_exception); + test_tuple_int_int_int(check, check_exception); + + if constexpr (!is_permissive_v) { + test_with_ranges(check, check_exception); + } + + test_adaptor(check, check_exception); +} + +auto test_format = [](basic_string_view expected, + type_identity_t> fmt, Args&&... args) { + basic_string out = format(fmt, forward(args)...); + assert(out == expected); +}; + +auto test_format_exception = [](string_view, basic_string_view, Args&&...) { + // After P2216 most exceptions thrown by format become ill-formed. + // Therefore this tests does nothing. +}; + +auto test_vformat = []( + basic_string_view expected, basic_string_view fmt, Args&&... args) { + basic_string out = vformat(fmt, make_format_args>(args...)); + assert(out == expected); +}; + +auto test_vformat_exception = []([[maybe_unused]] string_view what, + [[maybe_unused]] basic_string_view fmt, [[maybe_unused]] Args&&... args) { + try { + static_cast(vformat(fmt, make_format_args>(args...))); + assert(false); + } catch (const format_error&) { + } catch (...) { + assert(false); + } +}; + +int main() { + format_tests(test_format, test_format_exception); + format_tests(test_format, test_format_exception); + + format_tests(test_vformat, test_vformat_exception); + format_tests(test_vformat, test_vformat_exception); +} diff --git a/tests/std/tests/P2286R8_text_formatting_range_set/env.lst b/tests/std/tests/P2286R8_text_formatting_range_set/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_range_set/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp new file mode 100644 index 00000000000..0ed39efdce9 --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp @@ -0,0 +1,1773 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// derived from libc++'s test files: +// * support/format.functions.common.h +// * std/utilities/format/format.range/format.range.fmtset/format.functions.tests.h +// * std/utilities/format/format.range/format.range.fmtset/format.functions.format.pass.cpp +// * std/utilities/format/format.range/format.range.fmtset/format.functions.vformat.pass.cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; + +#define CSTR(Str) TYPED_LITERAL(CharT, Str) +#define STR(Str) basic_string(CSTR(Str)) +#define SV(Str) basic_string_view(CSTR(Str)) + +namespace detail { + consteval string_view get_format_types() noexcept { + return "aAbBcdeEfFgGopPsxX?"; + } + + template + basic_string get_colons() { + return basic_string(N, CharT(':')); + } + + template + vector> fmt_invalid_types(string_view valid) { + vector> result; + if constexpr (is_permissive_v) { + for (const char c : get_format_types()) { + if (valid.find(c) == string_view::npos) { + result.push_back(format(SV("{{{}{}}}"), get_colons(), c)); + } + } + } else { + // ranges::to is not available in C++20. + ranges::copy(get_format_types() | views::filter([&](char type) { + return valid.find(type) == string_view::npos; + }) | views::transform([&](char type) { return format(SV("{{{}{}}}"), get_colons(), type); }), + back_inserter(result)); + } + return result; + } +} // namespace detail + +// Creates format string for the invalid types. +// +// valid contains a list of types that are valid. +// +// The return value is a collection of basic_strings, instead of +// basic_string_views since the values are temporaries. +template +vector> fmt_invalid_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +// Like fmt_invalid_types but when the format spec is for an underlying formatter. +template +vector> fmt_invalid_nested_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +template +struct context {}; + +template <> +struct context { + using type = format_context; +}; + +template <> +struct context { + using type = wformat_context; +}; + +template +using context_t = context::type; + +// A user-defined type used to test the handle formatter. +enum class status : uint16_t { foo = 0xAAAA, bar = 0x5555, foobar = 0xAA55 }; + +// The formatter for a user-defined type used to test the handle formatter. +template +struct formatter { + // During the 2023 Issaquah meeting LEWG made it clear a formatter is + // required to call its parse function. LWG3892 Adds the wording for that + // requirement. Therefore this formatter is initialized in an invalid state. + // A call to parse sets it in a valid state and a call to format validates + // the state. + int type = -1; + + constexpr auto parse(basic_format_parse_context& parse_ctx) -> decltype(parse_ctx.begin()) { + auto begin = parse_ctx.begin(); + auto end = parse_ctx.end(); + type = 0; + if (begin == end) { + return begin; + } + + switch (*begin) { + case CharT('x'): + break; + case CharT('X'): + type = 1; + break; + case CharT('s'): + type = 2; + break; + case CharT('}'): + return begin; + default: + throw_format_error("The type option contains an invalid value for a status formatting argument"); + } + + ++begin; + if (begin != end && *begin != CharT('}')) { + throw_format_error("The format specifier should consume the input or end with a '}'"); + } + + return begin; + } + + template + auto format(status s, basic_format_context& ctx) const -> decltype(ctx.out()) { + const char* names[] = {"foo", "bar", "foobar"}; + char buffer[7]; + const char* pbegin = names[0]; + const char* pend = names[0]; + switch (type) { + case -1: + throw_format_error("The formatter's parse function has not been called."); + + case 0: + pbegin = buffer; + buffer[0] = '0'; + buffer[1] = 'x'; + pend = to_chars(&buffer[2], end(buffer), static_cast(s), 16).ptr; + buffer[6] = '\0'; + break; + + case 1: + pbegin = buffer; + buffer[0] = '0'; + buffer[1] = 'X'; + pend = to_chars(&buffer[2], end(buffer), static_cast(s), 16).ptr; + transform(static_cast(&buffer[2]), pend, &buffer[2], + [](char c) { return static_cast(toupper(c)); }); + buffer[6] = '\0'; + break; + + case 2: + switch (s) { + case status::foo: + pbegin = names[0]; + break; + case status::bar: + pbegin = names[1]; + break; + case status::foobar: + pbegin = names[2]; + break; + } + pend = pbegin + strlen(pbegin); + break; + } + +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(push) +#pragma warning(disable : 4365) +#endif // !defined(__clang__) && !defined(__EDG__) + return copy(pbegin, pend, ctx.out()); +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(pop) +#endif // !defined(__clang__) && !defined(__EDG__) + } + +private: + [[noreturn]] void throw_format_error(const char* s) const { + throw format_error(s); + } +}; + + +// +// Char +// + +template +void test_char_default(TestFunction check, ExceptionTest check_exception) { + set input{CharT('a'), CharT('c'), CharT('b')}; // input not sorted. + + // Note when no range-underlying-spec is present the char is escaped, + check(SV("{'a', 'b', 'c'}"), SV("{}"), input); + check(SV("{'a', 'b', 'c'}^42"), SV("{}^42"), input); + check(SV("{'a', 'b', 'c'}^42"), SV("{:}^42"), input); + // when one is present there is no escaping, + check(SV("{a, b, c}"), SV("{::}"), input); + check(SV("{a, b, c}"), SV("{::<}"), input); + // unless forced by the type specifier. + check(SV("{'a', 'b', 'c'}"), SV("{::?}"), input); + check(SV("{'a', 'b', 'c'}"), SV("{::20}"), input); + + check(SV("{'a', 'b', 'c'} "), SV("{:{}}"), input, 20); + check(SV("{'a', 'b', 'c'}*****"), SV("{:*<{}}"), input, 20); + check(SV("__{'a', 'b', 'c'}___"), SV("{:_^{}}"), input, 20); + check(SV("#####{'a', 'b', 'c'}"), SV("{:#>{}}"), input, 20); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__'a', 'b', 'c'___"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + + // ***** Only underlying has a format-spec + check(SV("{a , b , c }"), SV("{::4}"), input); + check(SV("{a***, b***, c***}"), SV("{::*<4}"), input); + check(SV("{_a__, _b__, _c__}"), SV("{::_^4}"), input); + check(SV("{:::a, :::b, :::c}"), SV("{:::>4}"), input); + + check(SV("{a , b , c }"), SV("{::{}}"), input, 4); + check(SV("{a***, b***, c***}"), SV("{::*<{}}"), input, 4); + check(SV("{_a__, _b__, _c__}"), SV("{::_^{}}"), input, 4); + check(SV("{:::a, :::b, :::c}"), SV("{:::>{}}"), input, 4); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier for a character does not allow the sign option", SV("{::-}"), input); + check_exception("The format specifier for a character does not allow the sign option", SV("{::+}"), input); + check_exception("The format specifier for a character does not allow the sign option", SV("{:: }"), input); + + check(SV("{97, 98, 99}"), SV("{::-d}"), input); + check(SV("{+97, +98, +99}"), SV("{::+d}"), input); + check(SV("{ 97, 98, 99}"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception( + "The format specifier for a character does not allow the alternate form option", SV("{::#}"), input); + + check(SV("{0x61, 0x62, 0x63}"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("The format specifier for a character does not allow the zero-padding option", SV("{::05}"), input); + + check(SV("{00141, 00142, 00143}"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("{a, b, c}"), SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBcdoxX?")) { + check_exception("The type option contains an invalid value for a character formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{:a, :b, :c}^^^"), SV("{:^^17::>2}"), input); + check(SV("^^{:a, :b, :c}^^^"), SV("{:^^{}::>2}"), input, 17); + check(SV("^^{:a, :b, :c}^^^"), SV("{:^^{}::>{}}"), input, 17, 2); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>2}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 17); +} + +// A set can be written as a string, based on +// [tab:formatter.range.type] +// s T shall be charT. ... +// This does not seem very useful, but it is allowed. +template +void test_char_string(TestFunction check, [[maybe_unused]] ExceptionTest check_exception) { + set input{CharT('a'), CharT('c'), CharT('b')}; // input not sorted. + + check(SV("abc"), SV("{:s}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("abc "), SV("{:6s}"), input); + check(SV("abc***"), SV("{:*<6s}"), input); + check(SV("_abc__"), SV("{:_^6s}"), input); + check(SV("###abc"), SV("{:#>6s}"), input); + + check(SV("abc "), SV("{:{}s}"), input, 6); + check(SV("abc***"), SV("{:*<{}s}"), input, 6); + check(SV("_abc__"), SV("{:_^{}s}"), input, 6); + check(SV("###abc"), SV("{:#>{}s}"), input, 6); + + check_exception("The format string contains an invalid escape sequence", SV("{:} fmt : fmt_invalid_nested_types("bBcdoxX?")) { + check_exception("The type option contains an invalid value for a character formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check_exception("Type s and an underlying format specification can't be used together", SV("{:5s:5}"), input); +} + +// A set can be written as a debug_string, based on +// [tab:formatter.range.type] +// ?s T shall be charT. ... +// This does not seem very useful, but it is allowed. +template +void test_char_escaped_string(TestFunction check, [[maybe_unused]] ExceptionTest check_exception) { + set input{CharT('a'), CharT('c'), CharT('b')}; // input not sorted. + + check(SV("\"abc\""), SV("{:?s}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"("abc" )"), SV("{:8?s}"), input); + check(SV(R"("abc"***)"), SV("{:*<8?s}"), input); + check(SV(R"(_"abc"__)"), SV("{:_^8?s}"), input); + check(SV(R"(###"abc")"), SV("{:#>8?s}"), input); + + check(SV(R"("abc" )"), SV("{:{}?s}"), input, 8); + check(SV(R"("abc"***)"), SV("{:*<{}?s}"), input, 8); + check(SV(R"(_"abc"__)"), SV("{:_^{}?s}"), input, 8); + check(SV(R"(###"abc")"), SV("{:#>{}?s}"), input, 8); + + check_exception("The format string contains an invalid escape sequence", SV("{:} +void test_char(TestFunction check, ExceptionTest check_exception) { + test_char_default(check, check_exception); + test_char_string(check, check_exception); + test_char_escaped_string(check, check_exception); +} + +// +// char -> wchar_t +// + +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +template +void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { + set input{'a', 'c', 'b'}; // input not sorted. + + using CharT = wchar_t; + + // Note when no range-underlying-spec is present the char is escaped, + check(SV("{'a', 'b', 'c'}"), SV("{}"), input); + check(SV("{'a', 'b', 'c'}^42"), SV("{}^42"), input); + check(SV("{'a', 'b', 'c'}^42"), SV("{:}^42"), input); + // when one is present there is no escaping, + check(SV("{a, b, c}"), SV("{::}"), input); + check(SV("{a, b, c}"), SV("{::<}"), input); + // unless forced by the type specifier. + check(SV("{'a', 'b', 'c'}"), SV("{::?}"), input); + check(SV("{'a', 'b', 'c'}"), SV("{::20}"), input); + + check(SV("{'a', 'b', 'c'} "), SV("{:{}}"), input, 20); + check(SV("{'a', 'b', 'c'}*****"), SV("{:*<{}}"), input, 20); + check(SV("__{'a', 'b', 'c'}___"), SV("{:_^{}}"), input, 20); + check(SV("#####{'a', 'b', 'c'}"), SV("{:#>{}}"), input, 20); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__'a', 'b', 'c'___"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + + // ***** Only underlying has a format-spec + check(SV("{a , b , c }"), SV("{::4}"), input); + check(SV("{a***, b***, c***}"), SV("{::*<4}"), input); + check(SV("{_a__, _b__, _c__}"), SV("{::_^4}"), input); + check(SV("{:::a, :::b, :::c}"), SV("{:::>4}"), input); + + check(SV("{a , b , c }"), SV("{::{}}"), input, 4); + check(SV("{a***, b***, c***}"), SV("{::*<{}}"), input, 4); + check(SV("{_a__, _b__, _c__}"), SV("{::_^{}}"), input, 4); + check(SV("{:::a, :::b, :::c}"), SV("{:::>{}}"), input, 4); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier for a character does not allow the sign option", SV("{::-}"), input); + check_exception("The format specifier for a character does not allow the sign option", SV("{::+}"), input); + check_exception("The format specifier for a character does not allow the sign option", SV("{:: }"), input); + + check(SV("{97, 98, 99}"), SV("{::-d}"), input); + check(SV("{+97, +98, +99}"), SV("{::+d}"), input); + check(SV("{ 97, 98, 99}"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception( + "The format specifier for a character does not allow the alternate form option", SV("{::#}"), input); + + check(SV("{0x61, 0x62, 0x63}"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("The format specifier for a character does not allow the zero-padding option", SV("{::05}"), input); + + check(SV("{00141, 00142, 00143}"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("{a, b, c}"), SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBcdoxX?")) { + check_exception("The type option contains an invalid value for a character formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{:a, :b, :c}^^^"), SV("{:^^17::>2}"), input); + check(SV("^^{:a, :b, :c}^^^"), SV("{:^^{}::>2}"), input, 17); + check(SV("^^{:a, :b, :c}^^^"), SV("{:^^{}::>{}}"), input, 17, 2); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>2}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 17); + + // The types s and ?s may only be used when using range_formatter + // where the types T and charT are the same. This means this can't be used for + // range_formatter even when formatter has a + // debug-enabled specialization. + + using CharT = wchar_t; + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); +} +#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +// +// Bool +// + +template +void test_bool(TestFunction check, ExceptionTest check_exception) { + set input{true, false}; + + check(SV("{false, true}"), SV("{}"), input); + check(SV("{false, true}^42"), SV("{}^42"), input); + check(SV("{false, true}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{false, true} "), SV("{:18}"), input); + check(SV("{false, true}*****"), SV("{:*<18}"), input); + check(SV("__{false, true}___"), SV("{:_^18}"), input); + check(SV("#####{false, true}"), SV("{:#>18}"), input); + + check(SV("{false, true} "), SV("{:{}}"), input, 18); + check(SV("{false, true}*****"), SV("{:*<{}}"), input, 18); + check(SV("__{false, true}___"), SV("{:_^{}}"), input, 18); + check(SV("#####{false, true}"), SV("{:#>{}}"), input, 18); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__false, true___"), SV("{:_^16n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{false , true }"), SV("{::7}"), input); + check(SV("{false**, true***}"), SV("{::*<7}"), input); + check(SV("{_false_, _true__}"), SV("{::_^7}"), input); + check(SV("{::false, :::true}"), SV("{:::>7}"), input); + + check(SV("{false , true }"), SV("{::{}}"), input, 7); + check(SV("{false**, true***}"), SV("{::*<{}}"), input, 7); + check(SV("{_false_, _true__}"), SV("{::_^{}}"), input, 7); + check(SV("{::false, :::true}"), SV("{:::>{}}"), input, 7); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier for a bool does not allow the sign option", SV("{::-}"), input); + check_exception("The format specifier for a bool does not allow the sign option", SV("{::+}"), input); + check_exception("The format specifier for a bool does not allow the sign option", SV("{:: }"), input); + + check(SV("{0, 1}"), SV("{::-d}"), input); + check(SV("{+0, +1}"), SV("{::+d}"), input); + check(SV("{ 0, 1}"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception("The format specifier for a bool does not allow the alternate form option", SV("{::#}"), input); + + check(SV("{0x0, 0x1}"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("The format specifier for a bool does not allow the zero-padding option", SV("{::05}"), input); + + check(SV("{00000, 00001}"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("{false, true}"), SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBdosxX")) { + check_exception("The type option contains an invalid value for a bool formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{::false, :::true}^^^"), SV("{:^^23::>7}"), input); + check(SV("^^{::false, :::true}^^^"), SV("{:^^{}::>7}"), input, 23); + check(SV("^^{::false, :::true}^^^"), SV("{:^^{}::>{}}"), input, 23, 7); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 23); +} + +template +void test_bool_multiset(TestFunction check, ExceptionTest check_exception) { + multiset> input{true, false, true}; // unordered + + check(SV("{true, true, false}"), SV("{}"), input); + check(SV("{true, true, false}^42"), SV("{}^42"), input); + check(SV("{true, true, false}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{true, true, false} "), SV("{:24}"), input); + check(SV("{true, true, false}*****"), SV("{:*<24}"), input); + check(SV("__{true, true, false}___"), SV("{:_^24}"), input); + check(SV("#####{true, true, false}"), SV("{:#>24}"), input); + + check(SV("{true, true, false} "), SV("{:{}}"), input, 24); + check(SV("{true, true, false}*****"), SV("{:*<{}}"), input, 24); + check(SV("__{true, true, false}___"), SV("{:_^{}}"), input, 24); + check(SV("#####{true, true, false}"), SV("{:#>{}}"), input, 24); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__true, true, false___"), SV("{:_^22n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{true , true , false }"), SV("{::7}"), input); + check(SV("{true***, true***, false**}"), SV("{::*<7}"), input); + check(SV("{_true__, _true__, _false_}"), SV("{::_^7}"), input); + check(SV("{:::true, :::true, ::false}"), SV("{:::>7}"), input); + + check(SV("{true , true , false }"), SV("{::{}}"), input, 7); + check(SV("{true***, true***, false**}"), SV("{::*<{}}"), input, 7); + check(SV("{_true__, _true__, _false_}"), SV("{::_^{}}"), input, 7); + check(SV("{:::true, :::true, ::false}"), SV("{:::>{}}"), input, 7); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier for a bool does not allow the sign option", SV("{::-}"), input); + check_exception("The format specifier for a bool does not allow the sign option", SV("{::+}"), input); + check_exception("The format specifier for a bool does not allow the sign option", SV("{:: }"), input); + + check(SV("{1, 1, 0}"), SV("{::-d}"), input); + check(SV("{+1, +1, +0}"), SV("{::+d}"), input); + check(SV("{ 1, 1, 0}"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception("The format specifier for a bool does not allow the alternate form option", SV("{::#}"), input); + + check(SV("{0x1, 0x1, 0x0}"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("The format specifier for a bool does not allow the zero-padding option", SV("{::05}"), input); + + check(SV("{00001, 00001, 00000}"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("{true, true, false}"), SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBdosxX")) { + check_exception("The type option contains an invalid value for a bool formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{:::true, :::true, ::false}^^^"), SV("{:^^32::>7}"), input); + check(SV("^^{:::true, :::true, ::false}^^^"), SV("{:^^{}::>7}"), input, 32); + check(SV("^^{:::true, :::true, ::false}^^^"), SV("{:^^{}::>{}}"), input, 32, 7); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 32); +} + +// +// Integral +// + +template +void test_int(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("{-42, 1, 2, 42}"), SV("{}"), input); + check(SV("{-42, 1, 2, 42}^42"), SV("{}^42"), input); + check(SV("{-42, 1, 2, 42}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{-42, 1, 2, 42} "), SV("{:20}"), input); + check(SV("{-42, 1, 2, 42}*****"), SV("{:*<20}"), input); + check(SV("__{-42, 1, 2, 42}___"), SV("{:_^20}"), input); + check(SV("#####{-42, 1, 2, 42}"), SV("{:#>20}"), input); + + check(SV("{-42, 1, 2, 42} "), SV("{:{}}"), input, 20); + check(SV("{-42, 1, 2, 42}*****"), SV("{:*<{}}"), input, 20); + check(SV("__{-42, 1, 2, 42}___"), SV("{:_^{}}"), input, 20); + check(SV("#####{-42, 1, 2, 42}"), SV("{:#>{}}"), input, 20); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__-42, 1, 2, 42___"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{ -42, 1, 2, 42}"), SV("{::5}"), input); + check(SV("{-42**, 1****, 2****, 42***}"), SV("{::*<5}"), input); + check(SV("{_-42_, __1__, __2__, _42__}"), SV("{::_^5}"), input); + check(SV("{::-42, ::::1, ::::2, :::42}"), SV("{:::>5}"), input); + + check(SV("{ -42, 1, 2, 42}"), SV("{::{}}"), input, 5); + check(SV("{-42**, 1****, 2****, 42***}"), SV("{::*<{}}"), input, 5); + check(SV("{_-42_, __1__, __2__, _42__}"), SV("{::_^{}}"), input, 5); + check(SV("{::-42, ::::1, ::::2, :::42}"), SV("{:::>{}}"), input, 5); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check(SV("{-42, 1, 2, 42}"), SV("{::-}"), input); + check(SV("{-42, +1, +2, +42}"), SV("{::+}"), input); + check(SV("{-42, 1, 2, 42}"), SV("{:: }"), input); + + // *** alternate form *** + check(SV("{-0x2a, 0x1, 0x2, 0x2a}"), SV("{::#x}"), input); + + // *** zero-padding *** + check(SV("{-0042, 00001, 00002, 00042}"), SV("{::05}"), input); + check(SV("{-002a, 00001, 00002, 0002a}"), SV("{::05x}"), input); + check(SV("{-0x2a, 0x001, 0x002, 0x02a}"), SV("{::#05x}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("{-42, 1, 2, 42}"), SV("{::L}"), input); // does nothing in this test, but is accepted. + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBcdoxX")) { + check_exception("The type option contains an invalid value for an integer formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{::-42, ::::1, ::::2, :::42}^^^"), SV("{:^^33::>5}"), input); + check(SV("^^{::-42, ::::1, ::::2, :::42}^^^"), SV("{:^^{}::>5}"), input, 33); + check(SV("^^{::-42, ::::1, ::::2, :::42}^^^"), SV("{:^^{}::>{}}"), input, 33, 5); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 33); +} + +template +void test_int(TestFunction check, ExceptionTest check_exception) { + test_int(check, check_exception, set{1, 42, 2, -42}); // unsorted + test_int(check, check_exception, multiset{1, 42, 2, -42}); // unsorted +} + +// +// Floating point +// + +template +void test_floating_point(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("{-42.5, 0, 1.25, 42.5}"), SV("{}"), input); + check(SV("{-42.5, 0, 1.25, 42.5}^42"), SV("{}^42"), input); + check(SV("{-42.5, 0, 1.25, 42.5}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{-42.5, 0, 1.25, 42.5} "), SV("{:27}"), input); + check(SV("{-42.5, 0, 1.25, 42.5}*****"), SV("{:*<27}"), input); + check(SV("__{-42.5, 0, 1.25, 42.5}___"), SV("{:_^27}"), input); + check(SV("#####{-42.5, 0, 1.25, 42.5}"), SV("{:#>27}"), input); + + check(SV("{-42.5, 0, 1.25, 42.5} "), SV("{:{}}"), input, 27); + check(SV("{-42.5, 0, 1.25, 42.5}*****"), SV("{:*<{}}"), input, 27); + check(SV("__{-42.5, 0, 1.25, 42.5}___"), SV("{:_^{}}"), input, 27); + check(SV("#####{-42.5, 0, 1.25, 42.5}"), SV("{:#>{}}"), input, 27); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__-42.5, 0, 1.25, 42.5___"), SV("{:_^25n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{-42.5, 0, 1.25, 42.5}"), SV("{::5}"), input); + check(SV("{-42.5, 0****, 1.25*, 42.5*}"), SV("{::*<5}"), input); + check(SV("{-42.5, __0__, 1.25_, 42.5_}"), SV("{::_^5}"), input); + check(SV("{-42.5, ::::0, :1.25, :42.5}"), SV("{:::>5}"), input); + + check(SV("{-42.5, 0, 1.25, 42.5}"), SV("{::{}}"), input, 5); + check(SV("{-42.5, 0****, 1.25*, 42.5*}"), SV("{::*<{}}"), input, 5); + check(SV("{-42.5, __0__, 1.25_, 42.5_}"), SV("{::_^{}}"), input, 5); + check(SV("{-42.5, ::::0, :1.25, :42.5}"), SV("{:::>{}}"), input, 5); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check(SV("{-42.5, 0, 1.25, 42.5}"), SV("{::-}"), input); + check(SV("{-42.5, +0, +1.25, +42.5}"), SV("{::+}"), input); + check(SV("{-42.5, 0, 1.25, 42.5}"), SV("{:: }"), input); + + // *** alternate form *** + check(SV("{-42.5, 0., 1.25, 42.5}"), SV("{::#}"), input); + + // *** zero-padding *** + check(SV("{-42.5, 00000, 01.25, 042.5}"), SV("{::05}"), input); + check(SV("{-42.5, 0000., 01.25, 042.5}"), SV("{::#05}"), input); + + // *** precision *** + check(SV("{-42, 0, 1.2, 42}"), SV("{::.2}"), input); + check(SV("{-42.500, 0.000, 1.250, 42.500}"), SV("{::.3f}"), input); + + check(SV("{-42, 0, 1.2, 42}"), SV("{::.{}}"), input, 2); + check(SV("{-42.500, 0.000, 1.250, 42.500}"), SV("{::.{}f}"), input, 3); + + check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("{-42.5, 0, 1.25, 42.5}"), SV("{::L}"), input); // does not require locales present +#ifndef TEST_HAS_NO_LOCALIZATION +// TODO FMT Enable with locale testing active +#if 0 + locale::global(locale(LOCALE_fr_FR_UTF_8)); + check(SV("{-42,5, 0, 1,25, 42,5}"), SV("{::L}"), input); + + locale::global(locale(LOCALE_en_US_UTF_8)); + check(SV("{-42.5, 0, 1.25, 42.5}"), SV("{::L}"), input); + + locale::global(locale::classic()); +#endif +#endif // TEST_HAS_NO_LOCALIZATION + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("aAeEfFgG")) { + check_exception( + "The type option contains an invalid value for a floating-point formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{-42.5, ::::0, :1.25, :42.5}^^^"), SV("{:^^33::>5}"), input); + check(SV("^^{-42.5, ::::0, :1.25, :42.5}^^^"), SV("{:^^{}::>5}"), input, 33); + check(SV("^^{-42.5, ::::0, :1.25, :42.5}^^^"), SV("{:^^{}::>{}}"), input, 33, 5); + + check(SV("^^{::-42, ::::0, ::1.2, :::42}^^^"), SV("{:^^33::>5.2}"), input); + check(SV("^^{::-42, ::::0, ::1.2, :::42}^^^"), SV("{:^^{}::>5.2}"), input, 33); + check(SV("^^{::-42, ::::0, ::1.2, :::42}^^^"), SV("{:^^{}::>{}.2}"), input, 33, 5); + check(SV("^^{::-42, ::::0, ::1.2, :::42}^^^"), SV("{:^^{}::>{}.{}}"), input, 33, 5, 2); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5.2}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}.2}"), input, 33); + check_exception("The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}.{}}"), + input, 33, 5); +} + +template +void test_floating_point(TestFunction check, ExceptionTest check_exception) { + test_floating_point(check, check_exception, set{-42.5f, 0.0f, 1.25f, 42.5f}); + test_floating_point(check, check_exception, multiset{-42.5, 0.0, 1.25, 42.5}); + test_floating_point(check, check_exception, set{-42.5l, 0.0l, 1.25l, 42.5l}); +} + +// +// Pointer +// + +template +void test_pointer(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("{0x0}"), SV("{}"), input); + check(SV("{0x0}^42"), SV("{}^42"), input); + check(SV("{0x0}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{0x0} "), SV("{:10}"), input); + check(SV("{0x0}*****"), SV("{:*<10}"), input); + check(SV("__{0x0}___"), SV("{:_^10}"), input); + check(SV("#####{0x0}"), SV("{:#>10}"), input); + + check(SV("{0x0} "), SV("{:{}}"), input, 10); + check(SV("{0x0}*****"), SV("{:*<{}}"), input, 10); + check(SV("__{0x0}___"), SV("{:_^{}}"), input, 10); + check(SV("#####{0x0}"), SV("{:#>{}}"), input, 10); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("_0x0_"), SV("{:_^5n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{ 0x0}"), SV("{::5}"), input); + check(SV("{0x0**}"), SV("{::*<5}"), input); + check(SV("{_0x0_}"), SV("{::_^5}"), input); + check(SV("{::0x0}"), SV("{:::>5}"), input); + + check(SV("{ 0x0}"), SV("{::{}}"), input, 5); + check(SV("{0x0**}"), SV("{::*<{}}"), input, 5); + check(SV("{_0x0_}"), SV("{::_^{}}"), input, 5); + check(SV("{::0x0}"), SV("{:::>{}}"), input, 5); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check(SV("{0x0000}"), SV("{::06}"), input); + check(SV("{0x0000}"), SV("{::06p}"), input); + check(SV("{0X0000}"), SV("{::06P}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("pP")) { + check_exception("The type option contains an invalid value for a pointer formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{::0x0}^^^"), SV("{:^^12::>5}"), input); + check(SV("^^{::0x0}^^^"), SV("{:^^{}::>5}"), input, 12); + check(SV("^^{::0x0}^^^"), SV("{:^^{}::>{}}"), input, 12, 5); + + check(SV("^^{::0x0}^^^"), SV("{:^^12::>5}"), input); + check(SV("^^{::0x0}^^^"), SV("{:^^{}::>5}"), input, 12); + check(SV("^^{::0x0}^^^"), SV("{:^^{}::>{}}"), input, 12, 5); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 12); +} + +template +void test_pointer(TestFunction check, ExceptionTest check_exception) { + // Note nullptr_t can only be equality compared so not used in a set. + test_pointer(check, check_exception, unordered_set{static_cast(0)}); + test_pointer(check, check_exception, unordered_multiset{static_cast(0)}); +} + +// +// String +// + +template +void test_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV(R"({"Hello", "world"})"), SV("{}"), input); + check(SV(R"({"Hello", "world"}^42)"), SV("{}^42"), input); + check(SV(R"({"Hello", "world"}^42)"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"({"Hello", "world"} )"), SV("{:23}"), input); + check(SV(R"({"Hello", "world"}*****)"), SV("{:*<23}"), input); + check(SV(R"(__{"Hello", "world"}___)"), SV("{:_^23}"), input); + check(SV(R"(#####{"Hello", "world"})"), SV("{:#>23}"), input); + + check(SV(R"({"Hello", "world"} )"), SV("{:{}}"), input, 23); + check(SV(R"({"Hello", "world"}*****)"), SV("{:*<{}}"), input, 23); + check(SV(R"(__{"Hello", "world"}___)"), SV("{:_^{}}"), input, 23); + check(SV(R"(#####{"Hello", "world"})"), SV("{:#>{}}"), input, 23); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV(R"(_"Hello", "world"_)"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV(R"({Hello , world })"), SV("{::8}"), input); + check(SV(R"({Hello***, world***})"), SV("{::*<8}"), input); + check(SV(R"({_Hello__, _world__})"), SV("{::_^8}"), input); + check(SV(R"({:::Hello, :::world})"), SV("{:::>8}"), input); + + check(SV(R"({Hello , world })"), SV("{::{}}"), input, 8); + check(SV(R"({Hello***, world***})"), SV("{::*<{}}"), input, 8); + check(SV(R"({_Hello__, _world__})"), SV("{::_^{}}"), input, 8); + check(SV(R"({:::Hello, :::world})"), SV("{:::>{}}"), input, 8); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check(SV(R"({Hel, wor})"), SV("{::.3}"), input); + + check(SV(R"({Hel, wor})"), SV("{::.{}}"), input, 3); + + check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("s?")) { + check_exception("The type option contains an invalid value for a string formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV(R"(^^{:::Hello, :::world}^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^{:::Hello, :::world}^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^{:::Hello, :::world}^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check(SV(R"(^^{:::Hello, :::world}^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^{:::Hello, :::world}^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^{:::Hello, :::world}^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>8}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 25); +} + +template +void test_string(TestFunction check, ExceptionTest check_exception) { + test_string(check, check_exception, set{STR("Hello"), STR("world")}); + test_string(check, check_exception, set{SV("Hello"), SV("world")}); +} + +// +// Handle +// + +template +void test_status(TestFunction check, ExceptionTest check_exception) { + set input{status::foo, status::bar, status::foobar}; // unordered input + + check(SV("{0x5555, 0xaa55, 0xaaaa}"), SV("{}"), input); + check(SV("{0x5555, 0xaa55, 0xaaaa}^42"), SV("{}^42"), input); + check(SV("{0x5555, 0xaa55, 0xaaaa}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{0x5555, 0xaa55, 0xaaaa} "), SV("{:29}"), input); + check(SV("{0x5555, 0xaa55, 0xaaaa}*****"), SV("{:*<29}"), input); + check(SV("__{0x5555, 0xaa55, 0xaaaa}___"), SV("{:_^29}"), input); + check(SV("#####{0x5555, 0xaa55, 0xaaaa}"), SV("{:#>29}"), input); + + check(SV("{0x5555, 0xaa55, 0xaaaa} "), SV("{:{}}"), input, 29); + check(SV("{0x5555, 0xaa55, 0xaaaa}*****"), SV("{:*<{}}"), input, 29); + check(SV("__{0x5555, 0xaa55, 0xaaaa}___"), SV("{:_^{}}"), input, 29); + check(SV("#####{0x5555, 0xaa55, 0xaaaa}"), SV("{:#>{}}"), input, 29); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__0x5555, 0xaa55, 0xaaaa___"), SV("{:_^27n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check_exception("The type option contains an invalid value for a status formatting argument", SV("{::*<7}"), input); + + check(SV("{0x5555, 0xaa55, 0xaaaa}"), SV("{::x}"), input); + check(SV("{0X5555, 0XAA55, 0XAAAA}"), SV("{::X}"), input); + check(SV("{bar, foobar, foo}"), SV("{::s}"), input); + + // ***** Both have a format-spec + check(SV("^^{0X5555, 0XAA55, 0XAAAA}^^^"), SV("{:^^29:X}"), input); + check(SV("^^{0X5555, 0XAA55, 0XAAAA}^^^"), SV("{:^^{}:X}"), input, 29); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:X}"), input); +} + +// +// Pair +// + +template +void test_pair_tuple(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("{(1, 'a'), (42, '*')}"), SV("{}"), input); + check(SV("{(1, 'a'), (42, '*')}^42"), SV("{}^42"), input); + check(SV("{(1, 'a'), (42, '*')}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{(1, 'a'), (42, '*')} "), SV("{:26}"), input); + check(SV("{(1, 'a'), (42, '*')}*****"), SV("{:*<26}"), input); + check(SV("__{(1, 'a'), (42, '*')}___"), SV("{:_^26}"), input); + check(SV("#####{(1, 'a'), (42, '*')}"), SV("{:#>26}"), input); + + check(SV("{(1, 'a'), (42, '*')} "), SV("{:{}}"), input, 26); + check(SV("{(1, 'a'), (42, '*')}*****"), SV("{:*<{}}"), input, 26); + check(SV("__{(1, 'a'), (42, '*')}___"), SV("{:_^{}}"), input, 26); + check(SV("#####{(1, 'a'), (42, '*')}"), SV("{:#>{}}"), input, 26); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__(1, 'a'), (42, '*')___"), SV("{:_^24n}"), input); + check(SV("____1: 'a', 42: '*'_____"), SV("{:_^24nm}"), input); + + // *** type *** + check(SV("____{1: 'a', 42: '*'}_____"), SV("{:_^26m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{(1, 'a') , (42, '*') }"), SV("{::11}"), input); + check(SV("{(1, 'a')***, (42, '*')**}"), SV("{::*<11}"), input); + check(SV("{_(1, 'a')__, _(42, '*')_}"), SV("{::_^11}"), input); + check(SV("{###(1, 'a'), ##(42, '*')}"), SV("{::#>11}"), input); + + check(SV("{(1, 'a') , (42, '*') }"), SV("{::{}}"), input, 11); + check(SV("{(1, 'a')***, (42, '*')**}"), SV("{::*<{}}"), input, 11); + check(SV("{_(1, 'a')__, _(42, '*')_}"), SV("{::_^{}}"), input, 11); + check(SV("{###(1, 'a'), ##(42, '*')}"), SV("{::#>{}}"), input, 11); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + check(SV("{1: 'a', 42: '*'}"), SV("{::m}"), input); + check(SV("{1, 'a', 42, '*'}"), SV("{::n}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::s}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::?s}"), input); + + for (basic_string_view fmt : fmt_invalid_nested_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{###(1, 'a'), ##(42, '*')}^^^"), SV("{:^^31:#>11}"), input); + check(SV("^^{###(1, 'a'), ##(42, '*')}^^^"), SV("{:^^31:#>11}"), input); + check(SV("^^{###(1, 'a'), ##(42, '*')}^^^"), SV("{:^^{}:#>11}"), input, 31); + check(SV("^^{###(1, 'a'), ##(42, '*')}^^^"), SV("{:^^{}:#>{}}"), input, 31, 11); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 31); + + check(SV("1: 'a', 42: '*'"), SV("{:n:m}"), input); + check(SV("1, 'a', 42, '*'"), SV("{:n:n}"), input); + check(SV("{1: 'a', 42: '*'}"), SV("{:m:m}"), input); + check(SV("{1: 'a', 42: '*'}"), SV("{:m:n}"), input); +} + +template +void test_pair_tuple(TestFunction check, ExceptionTest check_exception) { + test_pair_tuple(check, check_exception, set{make_pair(1, CharT('a')), make_pair(42, CharT('*'))}); + test_pair_tuple(check, check_exception, set{make_tuple(1, CharT('a')), make_tuple(42, CharT('*'))}); +} + +// +// Tuple 1 +// + +template +void test_tuple_int(TestFunction check, ExceptionTest check_exception) { + set input{make_tuple(42), make_tuple(99)}; + + check(SV("{(42), (99)}"), SV("{}"), input); + check(SV("{(42), (99)}^42"), SV("{}^42"), input); + check(SV("{(42), (99)}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{(42), (99)} "), SV("{:17}"), input); + check(SV("{(42), (99)}*****"), SV("{:*<17}"), input); + check(SV("__{(42), (99)}___"), SV("{:_^17}"), input); + check(SV("#####{(42), (99)}"), SV("{:#>17}"), input); + + check(SV("{(42), (99)} "), SV("{:{}}"), input, 17); + check(SV("{(42), (99)}*****"), SV("{:*<{}}"), input, 17); + check(SV("__{(42), (99)}___"), SV("{:_^{}}"), input, 17); + check(SV("#####{(42), (99)}"), SV("{:#>{}}"), input, 17); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__(42), (99)___"), SV("{:_^15n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{(42) , (99) }"), SV("{::7}"), input); + check(SV("{(42)***, (99)***}"), SV("{::*<7}"), input); + check(SV("{_(42)__, _(99)__}"), SV("{::_^7}"), input); + check(SV("{###(42), ###(99)}"), SV("{::#>7}"), input); + + check(SV("{(42) , (99) }"), SV("{::{}}"), input, 7); + check(SV("{(42)***, (99)***}"), SV("{::*<{}}"), input, 7); + check(SV("{_(42)__, _(99)__}"), SV("{::_^{}}"), input, 7); + check(SV("{###(42), ###(99)}"), SV("{::#>{}}"), input, 7); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + check(SV("{42, 99}"), SV("{::n}"), input); + check_exception("Type m requires a pair or a tuple with two elements", SV("{::m}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::s}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::?s}"), input); + + for (basic_string_view fmt : fmt_invalid_nested_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{###(42), ###(99)}^^^"), SV("{:^^23:#>7}"), input); + check(SV("^^{###(42), ###(99)}^^^"), SV("{:^^23:#>7}"), input); + check(SV("^^{###(42), ###(99)}^^^"), SV("{:^^{}:#>7}"), input, 23); + check(SV("^^{###(42), ###(99)}^^^"), SV("{:^^{}:#>{}}"), input, 23, 7); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 23); +} + +// +// Tuple 3 +// + +template +void test_tuple_int_int_int(TestFunction check, ExceptionTest check_exception) { + set input{make_tuple(42, 99, 0), make_tuple(1, 10, 100)}; // unordered + + check(SV("{(1, 10, 100), (42, 99, 0)}"), SV("{}"), input); + check(SV("{(1, 10, 100), (42, 99, 0)}^42"), SV("{}^42"), input); + check(SV("{(1, 10, 100), (42, 99, 0)}^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("{(1, 10, 100), (42, 99, 0)} "), SV("{:32}"), input); + check(SV("{(1, 10, 100), (42, 99, 0)}*****"), SV("{:*<32}"), input); + check(SV("__{(1, 10, 100), (42, 99, 0)}___"), SV("{:_^32}"), input); + check(SV("#####{(1, 10, 100), (42, 99, 0)}"), SV("{:#>32}"), input); + + check(SV("{(1, 10, 100), (42, 99, 0)} "), SV("{:{}}"), input, 32); + check(SV("{(1, 10, 100), (42, 99, 0)}*****"), SV("{:*<{}}"), input, 32); + check(SV("__{(1, 10, 100), (42, 99, 0)}___"), SV("{:_^{}}"), input, 32); + check(SV("#####{(1, 10, 100), (42, 99, 0)}"), SV("{:#>{}}"), input, 32); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__(1, 10, 100), (42, 99, 0)___"), SV("{:_^30n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("{(1, 10, 100) , (42, 99, 0) }"), SV("{::14}"), input); + check(SV("{(1, 10, 100)**, (42, 99, 0)***}"), SV("{::*<14}"), input); + check(SV("{_(1, 10, 100)_, _(42, 99, 0)__}"), SV("{::_^14}"), input); + check(SV("{##(1, 10, 100), ###(42, 99, 0)}"), SV("{::#>14}"), input); + + check(SV("{(1, 10, 100) , (42, 99, 0) }"), SV("{::{}}"), input, 14); + check(SV("{(1, 10, 100)**, (42, 99, 0)***}"), SV("{::*<{}}"), input, 14); + check(SV("{_(1, 10, 100)_, _(42, 99, 0)__}"), SV("{::_^{}}"), input, 14); + check(SV("{##(1, 10, 100), ###(42, 99, 0)}"), SV("{::#>{}}"), input, 14); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + check(SV("{1, 10, 100, 42, 99, 0}"), SV("{::n}"), input); + check_exception("Type m requires a pair or a tuple with two elements", SV("{::m}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::s}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{::?s}"), input); + + for (basic_string_view fmt : fmt_invalid_nested_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^{##(1, 10, 100), ###(42, 99, 0)}^^^"), SV("{:^^37:#>14}"), input); + check(SV("^^{##(1, 10, 100), ###(42, 99, 0)}^^^"), SV("{:^^37:#>14}"), input); + check(SV("^^{##(1, 10, 100), ###(42, 99, 0)}^^^"), SV("{:^^{}:#>14}"), input, 37); + check(SV("^^{##(1, 10, 100), ###(42, 99, 0)}^^^"), SV("{:^^{}:#>{}}"), input, 37, 14); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 37); +} + +// +// Adaptor +// + +class adaptor { + using adaptee = set; + +public: + using key_type = typename adaptee::key_type; + using iterator = typename adaptee::iterator; + + iterator begin() { + return data_.begin(); + } + iterator end() { + return data_.end(); + } + + explicit adaptor(set&& data) : data_(move(data)) {} + +private: + adaptee data_; +}; + +static_assert(format_kind == range_format::set); + +template +void test_adaptor(TestFunction check, ExceptionTest check_exception) { + test_int(check, check_exception, adaptor{set{1, 42, 2, -42}}); +} + +// +// Driver +// + +template +void format_tests(TestFunction check, ExceptionTest check_exception) { + test_char(check, check_exception); + +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + if (same_as) { // avoid testing twice + test_char_to_wchar(check, check_exception); + } +#endif + test_bool(check, check_exception); + test_bool_multiset(check, check_exception); + test_int(check, check_exception); + test_floating_point(check, check_exception); + test_pointer(check, check_exception); + test_string(check, check_exception); + + test_status(check, check_exception); // Has its own handler with its own parser + + test_pair_tuple(check, check_exception); + test_tuple_int(check, check_exception); + test_tuple_int_int_int(check, check_exception); + + test_adaptor(check, check_exception); +} + +auto test_format = [](basic_string_view expected, + type_identity_t> fmt, Args&&... args) { + basic_string out = format(fmt, forward(args)...); + assert(out == expected); +}; + +auto test_format_exception = [](string_view, basic_string_view, Args&&...) { + // After P2216 most exceptions thrown by format become ill-formed. + // Therefore this tests does nothing. +}; + +auto test_vformat = []( + basic_string_view expected, basic_string_view fmt, Args&&... args) { + basic_string out = vformat(fmt, make_format_args>(args...)); + assert(out == expected); +}; + +auto test_vformat_exception = []([[maybe_unused]] string_view what, + [[maybe_unused]] basic_string_view fmt, [[maybe_unused]] Args&&... args) { + try { + static_cast(vformat(fmt, make_format_args>(args...))); + assert(false); + } catch (const format_error&) { + } catch (...) { + assert(false); + } +}; + +int main() { + format_tests(test_format, test_format_exception); + format_tests(test_format, test_format_exception); + + format_tests(test_vformat, test_vformat_exception); + format_tests(test_vformat, test_vformat_exception); +} diff --git a/tests/std/tests/P2286R8_text_formatting_range_string/env.lst b/tests/std/tests/P2286R8_text_formatting_range_string/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_range_string/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp new file mode 100644 index 00000000000..7061a0bb9af --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp @@ -0,0 +1,563 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// derived from libc++'s test files: +// * support/format.functions.common.h +// * std/utilities/format/format.range/format.range.fmtstr/format.functions.tests.h +// * std/utilities/format/format.range/format.range.fmtstr/format.functions.format.pass.cpp +// * std/utilities/format/format.range/format.range.fmtstr/format.functions.vformat.pass.cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; + +#define CSTR(Str) TYPED_LITERAL(CharT, Str) +#define STR(Str) basic_string(CSTR(Str)) +#define SV(Str) basic_string_view(CSTR(Str)) + +namespace detail { + consteval string_view get_format_types() noexcept { + return "aAbBcdeEfFgGopPsxX?"; + } + + template + basic_string get_colons() { + return basic_string(N, CharT(':')); + } + + template + vector> fmt_invalid_types(string_view valid) { + vector> result; + if constexpr (is_permissive_v) { + for (const char c : get_format_types()) { + if (valid.find(c) == string_view::npos) { + result.push_back(format(SV("{{{}{}}}"), get_colons(), c)); + } + } + } else { + // ranges::to is not available in C++20. + ranges::copy(get_format_types() | views::filter([&](char type) { + return valid.find(type) == string_view::npos; + }) | views::transform([&](char type) { return format(SV("{{{}{}}}"), get_colons(), type); }), + back_inserter(result)); + } + return result; + } +} // namespace detail + +// Creates format string for the invalid types. +// +// valid contains a list of types that are valid. +// +// The return value is a collection of basic_strings, instead of +// basic_string_views since the values are temporaries. +template +vector> fmt_invalid_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +// Like fmt_invalid_types but when the format spec is for an underlying formatter. +template +vector> fmt_invalid_nested_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +template +struct context {}; + +template <> +struct context { + using type = format_context; +}; + +template <> +struct context { + using type = wformat_context; +}; + +template +using context_t = context::type; + + +// +// Types +// + +template +class test_range_format_string { +public: + explicit test_range_format_string(Container str) : str_(move(str)) {} + + typename Container::const_iterator begin() const { + return str_.begin(); + } + typename Container::const_iterator end() const { + return str_.end(); + } + +private: + Container str_; +}; + +template +constexpr range_format std::format_kind> = range_format::string; + +template +class test_range_format_debug_string { +public: + explicit test_range_format_debug_string(Container str) : str_(move(str)) {} + + typename Container::const_iterator begin() const { + return str_.begin(); + } + typename Container::const_iterator end() const { + return str_.end(); + } + +private: + Container str_; +}; + +template +constexpr range_format format_kind> = range_format::debug_string; + +// +// String +// + +template +void test_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("hello"), SV("{}"), input); + check(SV("hello^42"), SV("{}^42"), input); + check(SV("hello^42"), SV("{:}^42"), input); + + // *** align-fill & width *** + check(SV("hello "), SV("{:10}"), input); + check(SV("hello*****"), SV("{:*<10}"), input); + check(SV("__hello___"), SV("{:_^10}"), input); + check(SV(":::::hello"), SV("{::>10}"), input); + + check(SV("hello "), SV("{:{}}"), input, 10); + check(SV("hello*****"), SV("{:*<{}}"), input, 10); + check(SV("__hello___"), SV("{:_^{}}"), input, 10); + check(SV(":::::hello"), SV("{::>{}}"), input, 10); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check(SV("hel"), SV("{:.3}"), input); + check(SV("hel"), SV("{:.{}}"), input, 3); + + check(SV("hel "), SV("{:5.3}"), input); + check(SV("hel "), SV("{:{}.{}}"), input, 5, 3); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** type *** + check(SV("hello"), SV("{:s}"), input); + check(SV("\"hello\""), SV("{:?}"), input); + for (basic_string_view fmt : fmt_invalid_types("s?")) { + check_exception("The type option contains an invalid value for a string formatting argument", fmt, input); + } +} + +template +void test_string(TestFunction check, ExceptionTest check_exception) { + // libc++ uses different containers for contiguous and non-contiguous ranges. + basic_string input = STR("hello"); + test_string(check, check_exception, test_range_format_string>{input}); + test_string(check, check_exception, test_range_format_string>{input}); + test_string( + check, check_exception, test_range_format_string>{list{input.begin(), input.end()}}); +} + +// +// String range +// + +template +void test_range_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV(R"([Hello, world])"), SV("{}"), input); + check(SV(R"([Hello, world]^42)"), SV("{}^42"), input); + check(SV(R"([Hello, world]^42)"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"([Hello, world] )"), SV("{:19}"), input); + check(SV(R"([Hello, world]*****)"), SV("{:*<19}"), input); + check(SV(R"(__[Hello, world]___)"), SV("{:_^19}"), input); + check(SV(R"(#####[Hello, world])"), SV("{:#>19}"), input); + + check(SV(R"([Hello, world] )"), SV("{:{}}"), input, 19); + check(SV(R"([Hello, world]*****)"), SV("{:*<{}}"), input, 19); + check(SV(R"(__[Hello, world]___)"), SV("{:_^{}}"), input, 19); + check(SV(R"(#####[Hello, world])"), SV("{:#>{}}"), input, 19); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV(R"(_Hello, world_)"), SV("{:_^14n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV(R"([Hello , world ])"), SV("{::8}"), input); + check(SV(R"([Hello***, world***])"), SV("{::*<8}"), input); + check(SV(R"([_Hello__, _world__])"), SV("{::_^8}"), input); + check(SV(R"([:::Hello, :::world])"), SV("{:::>8}"), input); + + check(SV(R"([Hello , world ])"), SV("{::{}}"), input, 8); + check(SV(R"([Hello***, world***])"), SV("{::*<{}}"), input, 8); + check(SV(R"([_Hello__, _world__])"), SV("{::_^{}}"), input, 8); + check(SV(R"([:::Hello, :::world])"), SV("{:::>{}}"), input, 8); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check(SV(R"([Hel, wor])"), SV("{::.3}"), input); + + check(SV(R"([Hel, wor])"), SV("{::.{}}"), input, 3); + + check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("s?")) { + check_exception("The type option contains an invalid value for a string formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>8}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 25); +} + +template +void test_range_string(TestFunction check, ExceptionTest check_exception) { + // libc++ uses different containers for contiguous and non-contiguous ranges. + array input{STR("Hello"), STR("world")}; + test_range_string(check, check_exception, + array{test_range_format_string>{input[0]}, + test_range_format_string>{input[1]}}); + test_range_string(check, check_exception, + array{test_range_format_string>{input[0]}, + test_range_format_string>{input[1]}}); + test_range_string(check, check_exception, + array{test_range_format_string>{list{input[0].begin(), input[0].end()}}, + test_range_format_string>{list{input[1].begin(), input[1].end()}}}); + test_range_string(check, check_exception, + list{test_range_format_string>{list{input[0].begin(), input[0].end()}}, + test_range_format_string>{list{input[1].begin(), input[1].end()}}}); +} + +// +// Debug string +// + +template +void test_debug_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("\"hello\""), SV("{}"), input); + check(SV("\"hello\"^42"), SV("{}^42"), input); + check(SV("\"hello\"^42"), SV("{:}^42"), input); + + // *** align-fill & width *** + check(SV("\"hello\" "), SV("{:12}"), input); + check(SV("\"hello\"*****"), SV("{:*<12}"), input); + check(SV("__\"hello\"___"), SV("{:_^12}"), input); + check(SV(":::::\"hello\""), SV("{::>12}"), input); + + check(SV("\"hello\" "), SV("{:{}}"), input, 12); + check(SV("\"hello\"*****"), SV("{:*<{}}"), input, 12); + check(SV("__\"hello\"___"), SV("{:_^{}}"), input, 12); + check(SV(":::::\"hello\""), SV("{::>{}}"), input, 12); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check(SV("\"he"), SV("{:.3}"), input); + check(SV("\"he"), SV("{:.{}}"), input, 3); + + check(SV("\"he "), SV("{:5.3}"), input); + check(SV("\"he "), SV("{:{}.{}}"), input, 5, 3); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** type *** + check(SV("\"hello\""), SV("{:s}"), input); // escape overrides the type option s + check(SV("\"hello\""), SV("{:?}"), input); + for (basic_string_view fmt : fmt_invalid_types("s?")) { + check_exception("The type option contains an invalid value for a string formatting argument", fmt, input); + } +} + +template +void test_debug_string(TestFunction check, ExceptionTest check_exception) { + // libc++ uses different containers for contiguous and non-contiguous ranges. + basic_string input = STR("hello"); + test_debug_string(check, check_exception, test_range_format_debug_string>{input}); + test_debug_string(check, check_exception, test_range_format_debug_string>{input}); + test_debug_string( + check, check_exception, test_range_format_debug_string>{list{input.begin(), input.end()}}); +} + +// +// Debug string range +// + +template +void test_range_debug_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"(["Hello", "world"] )"), SV("{:23}"), input); + check(SV(R"(["Hello", "world"]*****)"), SV("{:*<23}"), input); + check(SV(R"(__["Hello", "world"]___)"), SV("{:_^23}"), input); + check(SV(R"(#####["Hello", "world"])"), SV("{:#>23}"), input); + + check(SV(R"(["Hello", "world"] )"), SV("{:{}}"), input, 23); + check(SV(R"(["Hello", "world"]*****)"), SV("{:*<{}}"), input, 23); + check(SV(R"(__["Hello", "world"]___)"), SV("{:_^{}}"), input, 23); + check(SV(R"(#####["Hello", "world"])"), SV("{:#>{}}"), input, 23); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV(R"(_"Hello", "world"_)"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV(R"(["Hello" , "world" ])"), SV("{::10}"), input); + check(SV(R"(["Hello"***, "world"***])"), SV("{::*<10}"), input); + check(SV(R"([_"Hello"__, _"world"__])"), SV("{::_^10}"), input); + check(SV(R"([:::"Hello", :::"world"])"), SV("{:::>10}"), input); + + check(SV(R"(["Hello" , "world" ])"), SV("{::{}}"), input, 10); + check(SV(R"(["Hello"***, "world"***])"), SV("{::*<{}}"), input, 10); + check(SV(R"([_"Hello"__, _"world"__])"), SV("{::_^{}}"), input, 10); + check(SV(R"([:::"Hello", :::"world"])"), SV("{:::>{}}"), input, 10); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check(SV(R"(["He, "wo])"), SV("{::.3}"), input); + + check(SV(R"(["He, "wo])"), SV("{::.{}}"), input, 3); + + check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("s?")) { + check_exception("The type option contains an invalid value for a string formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^29::>10}"), input); + check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>10}"), input, 29); + check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>{}}"), input, 29, 10); + + check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^29::>10}"), input); + check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>10}"), input, 29); + check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>{}}"), input, 29, 10); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>10}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 29); +} + +template +void test_range_debug_string(TestFunction check, ExceptionTest check_exception) { + // libc++ uses different containers for contiguous and non-contiguous ranges. + array input{STR("Hello"), STR("world")}; + test_range_debug_string(check, check_exception, + array{test_range_format_debug_string>{input[0]}, + test_range_format_debug_string>{input[1]}}); + test_range_debug_string(check, check_exception, + array{test_range_format_debug_string>{input[0]}, + test_range_format_debug_string>{input[1]}}); + test_range_debug_string(check, check_exception, + array{test_range_format_debug_string>{list{input[0].begin(), input[0].end()}}, + test_range_format_debug_string>{list{input[1].begin(), input[1].end()}}}); + test_range_debug_string(check, check_exception, + list{test_range_format_debug_string>{list{input[0].begin(), input[0].end()}}, + test_range_format_debug_string>{list{input[1].begin(), input[1].end()}}}); +} + +// +// Driver +// + +template +void format_tests(TestFunction check, ExceptionTest check_exception) { + test_string(check, check_exception); + test_range_string(check, check_exception); + + test_debug_string(check, check_exception); + test_range_debug_string(check, check_exception); +} + +auto test_format = [](basic_string_view expected, + type_identity_t> fmt, Args&&... args) { + basic_string out = format(fmt, forward(args)...); + assert(out == expected); +}; + +auto test_format_exception = [](string_view, basic_string_view, Args&&...) { + // After P2216 most exceptions thrown by format become ill-formed. + // Therefore this tests does nothing. +}; + +auto test_vformat = []( + basic_string_view expected, basic_string_view fmt, Args&&... args) { + basic_string out = vformat(fmt, make_format_args>(args...)); + assert(out == expected); +}; + +auto test_vformat_exception = []([[maybe_unused]] string_view what, + [[maybe_unused]] basic_string_view fmt, [[maybe_unused]] Args&&... args) { + try { + static_cast(vformat(fmt, make_format_args>(args...))); + assert(false); + } catch (const format_error&) { + } catch (...) { + assert(false); + } +}; + +int main() { + format_tests(test_format, test_format_exception); + format_tests(test_format, test_format_exception); + + format_tests(test_vformat, test_vformat_exception); + format_tests(test_vformat, test_vformat_exception); +} diff --git a/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/env.lst b/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/test.cpp b/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/test.cpp new file mode 100644 index 00000000000..ddd78aa2f8b --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/test.cpp @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +#define CSTR(Str) TYPED_LITERAL(CharT, Str) +#define STR(Str) basic_string(CSTR(Str)) +#define SV(Str) basic_string_view(CSTR(Str)) + +// If a pair or tuple is not formattable as a tuple but an input range, it should be formattable as a range. +namespace not_formatted_as_tuple { + struct tag {}; + + template + constexpr auto begin(tuple>& t) noexcept { + return get<1>(t).begin(); + } + template + constexpr auto begin(const tuple>& t) noexcept { + return get<1>(t).begin(); + } + + template + constexpr auto end(tuple>& t) noexcept { + return get<1>(t).end(); + } + template + constexpr auto end(const tuple>& t) noexcept { + return get<1>(t).end(); + } + + template + constexpr auto begin(pair>& t) noexcept { + return get<1>(t).begin(); + } + template + constexpr auto begin(const pair>& t) noexcept { + return get<1>(t).begin(); + } + + template + constexpr auto end(pair>& t) noexcept { + return get<1>(t).end(); + } + template + constexpr auto end(const pair>& t) noexcept { + return get<1>(t).end(); + } +} // namespace not_formatted_as_tuple + +// If a pair or tuple is formattable both as a tuple and as an input range, it should be formattable as a tuple. +namespace formattable_as_tuple_and_range { + struct tag {}; + + template + constexpr auto begin(tuple>& t) noexcept { + return get<1>(t).begin(); + } + template + constexpr auto begin(const tuple>& t) noexcept { + return get<1>(t).begin(); + } + + template + constexpr auto end(tuple>& t) noexcept { + return get<1>(t).end(); + } + template + constexpr auto end(const tuple>& t) noexcept { + return get<1>(t).end(); + } + + template + constexpr auto begin(pair>& t) noexcept { + return get<1>(t).begin(); + } + template + constexpr auto begin(const pair>& t) noexcept { + return get<1>(t).begin(); + } + + template + constexpr auto end(pair>& t) noexcept { + return get<1>(t).end(); + } + template + constexpr auto end(const pair>& t) noexcept { + return get<1>(t).end(); + } +} // namespace formattable_as_tuple_and_range + +template +struct std::formatter { + constexpr auto parse(basic_format_parse_context& parse_ctx) { + if (parse_ctx.begin() != parse_ctx.end() && *parse_ctx.begin() != '}') { + throw format_error{"empty specs expected"}; + } + return parse_ctx.begin(); + } + + template + auto format(formattable_as_tuple_and_range::tag, FmtCtx& ctx) const { + ctx.advance_to(ranges::copy(SV("test-tag"), ctx.out()).out); + return ctx.out(); + } +}; + +template +void test() { + { + auto s = format(CSTR("{}"), make_tuple(not_formatted_as_tuple::tag{}, vector{3, 1, 4, 1, 5, 9})); + assert(s == SV("[3, 1, 4, 1, 5, 9]")); + } + { + auto s = format(CSTR("{}"), make_pair(not_formatted_as_tuple::tag{}, vector{3, 1, 4, 1, 5, 9})); + assert(s == SV("[3, 1, 4, 1, 5, 9]")); + } + { + auto s = format(CSTR("{}"), make_tuple(formattable_as_tuple_and_range::tag{}, vector{3, 1, 4, 1, 5, 9})); + assert(s == SV("(test-tag, [3, 1, 4, 1, 5, 9])")); + } + { + auto s = format(CSTR("{}"), make_pair(formattable_as_tuple_and_range::tag{}, vector{3, 1, 4, 1, 5, 9})); + assert(s == SV("(test-tag, [3, 1, 4, 1, 5, 9])")); + } +} + +int main() { + test(); + test(); +} From 65426f2c200f3d03778bc85f17c0a5e18d20c8bc Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Sun, 9 Jun 2024 23:43:32 +0800 Subject: [PATCH 03/11] Address @cpplearner's review comments Removing one improper `// exposition only` comment. Co-authored-by: S. B. Tam --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index beaf52712dd..bff6f6a292f 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -4318,7 +4318,7 @@ public: template <_RANGES input_range _Rng, class _CharT> struct _Range_default_formatter { private: - using _Set_type = _Fmt_maybe_const<_Rng, _CharT>; // exposition only + using _Set_type = _Fmt_maybe_const<_Rng, _CharT>; range_formatter>, _CharT> _Underlying; public: From 5482c1be2333f256f17c2cf72ee3e2f8afde6420 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 24 Jun 2024 13:03:52 -0700 Subject: [PATCH 04/11] `<__msvc_formatter.hpp>` is now dragging in declarations of `pair` and `tuple`. --- stl/inc/__msvc_formatter.hpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/stl/inc/__msvc_formatter.hpp b/stl/inc/__msvc_formatter.hpp index 942393699fa..e26fd1f9f40 100644 --- a/stl/inc/__msvc_formatter.hpp +++ b/stl/inc/__msvc_formatter.hpp @@ -321,12 +321,6 @@ template <_RANGES input_range _Rng, _Format_supported_charT _CharT> requires _Formatting_enabled_range<_Rng> struct formatter<_Rng, _CharT>; -_EXPORT_STD template -struct pair; - -_EXPORT_STD template -class tuple; - template <_Format_supported_charT _CharT, class _Ty1, class _Ty2> struct formatter, _CharT>; From de729b8b5dbb2ae487ddbed348970b4aa8379fcd Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 24 Jun 2024 14:07:19 -0700 Subject: [PATCH 05/11] Update the primary template for `_Range_default_formatter` to match the Standard. --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index fd805908335..180af4c59fa 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -4269,7 +4269,7 @@ concept _Const_formattable_range = template using _Fmt_maybe_const = conditional_t<_Const_formattable_range<_Rng, _CharT>, const _Rng, _Rng>; -template +template struct _Range_default_formatter; template <_RANGES input_range _Rng, class _CharT> From 449ec92eb13c1a43873d060d00b18472d2be7579 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 24 Jun 2024 13:20:08 -0700 Subject: [PATCH 06/11] Fix comments. --- stl/inc/__msvc_formatter.hpp | 2 +- stl/inc/format | 2 +- tests/std/tests/P2286R8_text_formatting_range_map/test.cpp | 2 +- .../std/tests/P2286R8_text_formatting_range_sequence/test.cpp | 2 +- tests/std/tests/P2286R8_text_formatting_range_set/test.cpp | 2 +- tests/std/tests/P2286R8_text_formatting_range_string/test.cpp | 2 +- .../P2286R8_text_formatting_tuple_disambiguation/test.cpp | 3 ++- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/stl/inc/__msvc_formatter.hpp b/stl/inc/__msvc_formatter.hpp index e26fd1f9f40..bcf1f6e689e 100644 --- a/stl/inc/__msvc_formatter.hpp +++ b/stl/inc/__msvc_formatter.hpp @@ -308,7 +308,7 @@ constexpr range_format format_kind<_Rng> = []() consteval { } }(); -// Specializations for pairs, tuples and ranges are forward-declared to avoid any risk of using the disabled primary +// Specializations for pairs, tuples, and ranges are forward-declared to avoid any risk of using the disabled primary // template. // Per LWG-3997, `_CharT` in library-provided `formatter` specializations is diff --git a/stl/inc/format b/stl/inc/format index 180af4c59fa..0dceebb0509 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -4363,7 +4363,7 @@ private: using _Range_type = _Maybe_const<_RANGES input_range, _Rng>; - formatter, _CharT> _Underlying; // avoid copy the string if possible + formatter, _CharT> _Underlying; // avoid copying the string if possible public: template diff --git a/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp index 49541648c77..ff6086f309a 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp @@ -1071,7 +1071,7 @@ auto test_format = [](basic_string_view expec auto test_format_exception = [](string_view, basic_string_view, Args&&...) { // After P2216 most exceptions thrown by format become ill-formed. - // Therefore this tests does nothing. + // Therefore this test does nothing. }; auto test_vformat = []( diff --git a/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp index ec93f259455..bb0cc4874f4 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp @@ -1962,7 +1962,7 @@ auto test_format = [](basic_string_view expec auto test_format_exception = [](string_view, basic_string_view, Args&&...) { // After P2216 most exceptions thrown by format become ill-formed. - // Therefore this tests does nothing. + // Therefore this test does nothing. }; auto test_vformat = []( diff --git a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp index 0ed39efdce9..a39e4b6258e 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp @@ -1744,7 +1744,7 @@ auto test_format = [](basic_string_view expec auto test_format_exception = [](string_view, basic_string_view, Args&&...) { // After P2216 most exceptions thrown by format become ill-formed. - // Therefore this tests does nothing. + // Therefore this test does nothing. }; auto test_vformat = []( diff --git a/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp index 7061a0bb9af..d4da360ffb6 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp @@ -534,7 +534,7 @@ auto test_format = [](basic_string_view expec auto test_format_exception = [](string_view, basic_string_view, Args&&...) { // After P2216 most exceptions thrown by format become ill-formed. - // Therefore this tests does nothing. + // Therefore this test does nothing. }; auto test_vformat = []( diff --git a/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/test.cpp b/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/test.cpp index ddd78aa2f8b..f3aa62ab79a 100644 --- a/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_tuple_disambiguation/test.cpp @@ -18,7 +18,8 @@ using namespace std; #define STR(Str) basic_string(CSTR(Str)) #define SV(Str) basic_string_view(CSTR(Str)) -// If a pair or tuple is not formattable as a tuple but an input range, it should be formattable as a range. +// If a pair or tuple is not formattable as a tuple but is formattable as an input range, +// it should be formattable as a range. namespace not_formatted_as_tuple { struct tag {}; From aaccf492017d98e824dcc1a115e0fbc31580aa01 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 25 Jun 2024 10:56:12 -0700 Subject: [PATCH 07/11] Drop `#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS` (keeping code within). --- tests/std/tests/P2286R8_text_formatting_range_map/test.cpp | 4 ---- tests/std/tests/P2286R8_text_formatting_range_set/test.cpp | 4 ---- 2 files changed, 8 deletions(-) diff --git a/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp index ff6086f309a..782372b86f8 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp @@ -323,7 +323,6 @@ void test_char(TestFunction check, ExceptionTest check_exception) { // char -> wchar_t // -#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS template void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { map input{{'a', 'A'}, {'c', 'C'}, {'b', 'B'}}; @@ -429,7 +428,6 @@ void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { check_exception( "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:#>{}}"), input, 44); } -#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS // // Bool @@ -1047,11 +1045,9 @@ void test_adaptor(TestFunction check, ExceptionTest check_exception) { template void format_tests(TestFunction check, ExceptionTest check_exception) { test_char(check, check_exception); -#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS if (same_as) { // avoid testing twice test_char_to_wchar(check, check_exception); } -#endif test_bool(check, check_exception); test_int(check, check_exception); test_floating_point(check, check_exception); diff --git a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp index a39e4b6258e..33fbe964e98 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp @@ -452,7 +452,6 @@ void test_char(TestFunction check, ExceptionTest check_exception) { // char -> wchar_t // -#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS template void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { set input{'a', 'c', 'b'}; // input not sorted. @@ -573,7 +572,6 @@ void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); } -#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS // // Bool @@ -1715,11 +1713,9 @@ template void format_tests(TestFunction check, ExceptionTest check_exception) { test_char(check, check_exception); -#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS if (same_as) { // avoid testing twice test_char_to_wchar(check, check_exception); } -#endif test_bool(check, check_exception); test_bool_multiset(check, check_exception); test_int(check, check_exception); From 944bbcb7bd08ef743d2a5569d7c9258cc167645b Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 25 Jun 2024 11:21:05 -0700 Subject: [PATCH 08/11] Down with typename! --- .../std/tests/P2286R8_text_formatting_range_map/test.cpp | 6 +++--- .../std/tests/P2286R8_text_formatting_range_set/test.cpp | 4 ++-- .../tests/P2286R8_text_formatting_range_string/test.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp index 782372b86f8..baa2115b514 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_map/test.cpp @@ -1014,9 +1014,9 @@ class adaptor { using adaptee = map; public: - using key_type = typename adaptee::key_type; - using mapped_type = typename adaptee::mapped_type; - using iterator = typename adaptee::iterator; + using key_type = adaptee::key_type; + using mapped_type = adaptee::mapped_type; + using iterator = adaptee::iterator; iterator begin() { return data_.begin(); diff --git a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp index 33fbe964e98..fd68538956b 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp @@ -1682,8 +1682,8 @@ class adaptor { using adaptee = set; public: - using key_type = typename adaptee::key_type; - using iterator = typename adaptee::iterator; + using key_type = adaptee::key_type; + using iterator = adaptee::iterator; iterator begin() { return data_.begin(); diff --git a/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp index d4da360ffb6..fd2dde14707 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp @@ -114,10 +114,10 @@ class test_range_format_string { public: explicit test_range_format_string(Container str) : str_(move(str)) {} - typename Container::const_iterator begin() const { + Container::const_iterator begin() const { return str_.begin(); } - typename Container::const_iterator end() const { + Container::const_iterator end() const { return str_.end(); } @@ -133,10 +133,10 @@ class test_range_format_debug_string { public: explicit test_range_format_debug_string(Container str) : str_(move(str)) {} - typename Container::const_iterator begin() const { + Container::const_iterator begin() const { return str_.begin(); } - typename Container::const_iterator end() const { + Container::const_iterator end() const { return str_.end(); } From 55f9f986ef9fff4a68a8d4b72db98c55b3f5b1b7 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 25 Jun 2024 11:27:12 -0700 Subject: [PATCH 09/11] Drop disabled test code. --- .../tests/P2286R8_text_formatting_range_set/test.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp index fd68538956b..b877547f3a0 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp @@ -995,18 +995,6 @@ void test_floating_point(TestFunction check, ExceptionTest check_exception, auto // *** locale-specific form *** check(SV("{-42.5, 0, 1.25, 42.5}"), SV("{::L}"), input); // does not require locales present -#ifndef TEST_HAS_NO_LOCALIZATION -// TODO FMT Enable with locale testing active -#if 0 - locale::global(locale(LOCALE_fr_FR_UTF_8)); - check(SV("{-42,5, 0, 1,25, 42,5}"), SV("{::L}"), input); - - locale::global(locale(LOCALE_en_US_UTF_8)); - check(SV("{-42.5, 0, 1.25, 42.5}"), SV("{::L}"), input); - - locale::global(locale::classic()); -#endif -#endif // TEST_HAS_NO_LOCALIZATION // *** type *** for (basic_string_view fmt : fmt_invalid_nested_types("aAeEfFgG")) { From cf2f7bc94eb59e285e4ef94c0f41b1a3bf4daa77 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 25 Jun 2024 11:30:57 -0700 Subject: [PATCH 10/11] Don't use `0` as a null pointer constant, even when casting. --- .../std/tests/P2286R8_text_formatting_range_sequence/test.cpp | 4 ++-- tests/std/tests/P2286R8_text_formatting_range_set/test.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp index bb0cc4874f4..7b6a76244c5 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_sequence/test.cpp @@ -1306,8 +1306,8 @@ void test_pointer(TestFunction check, ExceptionTest check_exception, auto&& inpu template void test_pointer(TestFunction check, ExceptionTest check_exception) { test_pointer(check, check_exception, array{nullptr}); - test_pointer(check, check_exception, array{static_cast(0)}); - test_pointer(check, check_exception, array{static_cast(0)}); + test_pointer(check, check_exception, array{static_cast(nullptr)}); + test_pointer(check, check_exception, array{static_cast(nullptr)}); } // diff --git a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp index b877547f3a0..98c7e9b8ea0 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_set/test.cpp @@ -1134,8 +1134,8 @@ void test_pointer(TestFunction check, ExceptionTest check_exception, auto&& inpu template void test_pointer(TestFunction check, ExceptionTest check_exception) { // Note nullptr_t can only be equality compared so not used in a set. - test_pointer(check, check_exception, unordered_set{static_cast(0)}); - test_pointer(check, check_exception, unordered_multiset{static_cast(0)}); + test_pointer(check, check_exception, unordered_set{static_cast(nullptr)}); + test_pointer(check, check_exception, unordered_multiset{static_cast(nullptr)}); } // From 62202a5ccc1b511865a6ed7fff727bdcaa69e8f5 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 25 Jun 2024 11:45:32 -0700 Subject: [PATCH 11/11] Restore `std::` when defining a partial specialization of `format_kind`. --- tests/std/tests/P2286R8_text_formatting_range_string/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp b/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp index fd2dde14707..18b028b1821 100644 --- a/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_range_string/test.cpp @@ -145,7 +145,7 @@ class test_range_format_debug_string { }; template -constexpr range_format format_kind> = range_format::debug_string; +constexpr range_format std::format_kind> = range_format::debug_string; // // String