Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement formatter specializations for pair and tuple #4438

Merged
merged 23 commits into from
Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fdbd709
Implement `formatter` specializations for `pair` and `tuple`
frederick-vs-ja Mar 3, 2024
611cbc9
Remove a forgotten unused variable, updating title of LLVM-83734
frederick-vs-ja Mar 3, 2024
431a8c2
Reuse `_Fill_align_and_width_specs`
frederick-vs-ja Mar 4, 2024
764f327
Add 2 unblockable libcxx tests
frederick-vs-ja Mar 4, 2024
f3c1b23
Address @cpplearner's review comments on product code
frederick-vs-ja Mar 5, 2024
9565bd1
Reduce scope of `_Writer_for_single` and rename it
frederick-vs-ja Mar 6, 2024
a4dda1b
Add one test to std, derived from libcxx tests
frederick-vs-ja Mar 6, 2024
2c4d8ea
Merge branch 'main' into formatter-pair-tuple
frederick-vs-ja Mar 9, 2024
ec52fae
Down with `typename`!
frederick-vs-ja Mar 9, 2024
4f56c02
Clarify comment about forward declarations.
StephanTLavavej Mar 13, 2024
dea3608
Cite LWG-3997 and use `_Format_supported_charT` for `pair` and `tuple`.
StephanTLavavej Mar 14, 2024
a35a5fa
Pre-existing: Cite LWG-3997 and use `_Format_supported_charT` for `_V…
StephanTLavavej Mar 14, 2024
2d49b8e
Style: Add newlines between non-chained if-statements (plus one more …
StephanTLavavej Mar 14, 2024
af1f948
Typo: `_Indice` => `_Indices`
StephanTLavavej Mar 14, 2024
252ef0c
Deleted assignment should return `_Tuple_formatter_base&`.
StephanTLavavej Mar 14, 2024
d26f24f
Pre-existing: Deleted assignment should return `formatter&`.
StephanTLavavej Mar 14, 2024
c27261b
Use a raw string literal to avoid escaping ":"
StephanTLavavej Mar 14, 2024
2174e18
`_Tuple_debug_format_setter` lambda => `_Set_tuple_debug_format` func…
StephanTLavavej Mar 14, 2024
aa840d3
`struct _Tuple_formatter_common_base` => `class`
StephanTLavavej Mar 14, 2024
7bc89b1
`auto&&... _Elems` => `auto&... _Elems` when applying a lambda to `_U…
StephanTLavavej Mar 14, 2024
daa27a1
cgmanifest.json: Update llvm-project commitHash to current submodule.
StephanTLavavej Mar 14, 2024
afa85e4
Fix test comment typos, cite N4971.
StephanTLavavej Mar 14, 2024
df0c262
Add `_Base` aliases for readability.
StephanTLavavej Mar 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions stl/inc/__msvc_formatter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,22 @@ struct formatter<basic_string_view<_CharT, _Traits>, _CharT>
}
#endif // _HAS_CXX23
};

#if _HAS_CXX23
_EXPORT_STD template <class, class>
struct pair;

_EXPORT_STD template <class...>
class tuple;

// Specializations for pairs and tuples are forward-declared to avoid mix and mismatch as possible.
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

template <class _CharT, class _Ty1, class _Ty2>
struct formatter<pair<_Ty1, _Ty2>, _CharT>;

template <class _CharT, class... _Types>
struct formatter<tuple<_Types...>, _CharT>;
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
#endif // _HAS_CXX23
_STD_END

#pragma pop_macro("new")
Expand Down
267 changes: 257 additions & 10 deletions stl/inc/format
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ _EMIT_STL_WARNING(STL4038, "The contents of <format> are available only with C++
#include <xstring>
#include <xutility>

#if _HAS_CXX23
#include <tuple>
#endif // _HAS_CXX23

#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
Expand Down Expand Up @@ -3958,6 +3962,259 @@ _NODISCARD size_t formatted_size(const locale& _Loc, const wformat_string<_Types
_FMT_P2286_END

#if _HAS_CXX23
enum class _Fmt_tuple_type : uint8_t { _None, _Key_value, _No_brackets };

template <class _CharT>
struct _Fill_align_and_width_specs { // used by pair, tuple, thread::id, and stacktrace_entry formatters
int _Width = -1;
int _Dynamic_width_index = -1;
_Fmt_align _Alignment = _Fmt_align::_None;
uint8_t _Fill_length = 1;
// At most one codepoint (so one char32_t or four utf-8 char8_t).
_CharT _Fill[4 / sizeof(_CharT)]{' '};
};

template <class _CharT, bool _IsTwoTuple>
class _Tuple_format_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 void _On_align(const _Fmt_align _Aln) {
_Specs._Alignment = _Aln;
}

constexpr void _On_fill(const basic_string_view<_CharT> _Sv) {
if (_Sv.size() > _STD size(_Specs._Fill)) {
_Throw_format_error("Invalid fill (too long).");
}
if (_Sv == _STATICALLY_WIDEN(_CharT, ":")) {
_Throw_format_error("Invalid fill \":\" for tuples and pairs.");
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
}
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

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<uint8_t>(_Sv.size());
}

constexpr void _On_width(const int _Width) {
_Specs._Width = _Width;
}

constexpr void _On_dynamic_width(const size_t _Arg_id) {
_Parse_ctx.check_arg_id(_Arg_id);
_Parse_ctx._Check_dynamic_spec_integral(_Arg_id);
_Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id);
}

constexpr void _On_dynamic_width(_Auto_id_tag) {
const size_t _Arg_id = _Parse_ctx.next_arg_id();
_Parse_ctx._Check_dynamic_spec_integral(_Arg_id);
_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.");
}

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) {
if (!_STD in_range<int>(_Idx)) {
_Throw_format_error("Dynamic width index is too large.");
}

return static_cast<int>(_Idx);
}
};

template <class _CharT, class _Callbacks_type>
_NODISCARD constexpr const _CharT* _Parse_tuple_format_specs(
const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) {
if (_Begin == _End || *_Begin == '}') {
return _Begin;
}

_Begin = _STD _Parse_align(_Begin, _End, _Callbacks);
if (_Begin == _End) {
return _Begin;
}

_Begin = _STD _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);
++_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{});
}

return _Begin;
}

inline constexpr auto _Tuple_debug_format_setter = [](auto& _Formatter, auto& _Parse_ctx) _STATIC_CALL_OPERATOR {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
_Formatter.parse(_Parse_ctx);
if constexpr (requires { _Formatter.set_debug_format(); }) {
_Formatter.set_debug_format();
}
};

template <class _CharT, formattable<_CharT>... _Types>
struct _Tuple_formatter_common_base {
private:
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
tuple<formatter<remove_cvref_t<_Types>, _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;

protected:
static constexpr bool _Is_const_formattable = (formattable<const _Types, _CharT> && ...);

template <class _FormatContext, class... _ArgTypes>
typename _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<size_t>(_Specs._Dynamic_width_index)));
}

basic_string<_CharT> _Tmp_buf;
basic_format_context<back_insert_iterator<basic_string<_CharT>>, _CharT> _Tmp_ctx{
_STD back_inserter(_Tmp_buf), {}, _Fmt_ctx._Get_lazy_locale()};

auto _Writer = [&](back_insert_iterator<basic_string<_CharT>> _Out) {
formatter<basic_string_view<_CharT>, _CharT> _Sv_formatter;
frederick-vs-ja marked this conversation as resolved.
Show resolved Hide resolved

auto _Writer_for_single = [&]<size_t _Idx>(auto& _Arg) {
if constexpr (_Idx != 0) {
_Out = _Sv_formatter.format(_Separator, _Tmp_ctx);
}
_Out = _STD get<_Idx>(_Underlying).format(_Arg, _Tmp_ctx);
};

_Out = _Sv_formatter.format(_Opening_bracket, _Tmp_ctx);
[&, _Writer_for_single]<size_t... _Indice>(index_sequence<_Indice...>) {
(_Writer_for_single.template operator()<_Indice>(_Args), ...);
}(index_sequence_for<_ArgTypes...>{});
_Out = _Sv_formatter.format(_Closing_bracket, _Tmp_ctx);

return _Out;
};

(void) _STD _Write_aligned(_Tmp_ctx.out(), 0, _Basic_format_specs<_CharT>{}, _Fmt_align::_None, _Writer);
frederick-vs-ja marked this conversation as resolved.
Show resolved Hide resolved
return _STD _Write_aligned(_Fmt_ctx.out(), static_cast<int>(_Tmp_buf.size()), _Format_specs, _Fmt_align::_Left,
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
[&](typename _FormatContext::iterator _Out) {
return _STD _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Tmp_buf});
});
}

public:
constexpr void set_separator(const 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 {
_Opening_bracket = _Opening;
_Closing_bracket = _Closing;
}

template <class _ParseContext>
constexpr typename _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.");
}

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()));
_STD apply([&_Ctx](auto&&... _Elems) { (_Tuple_debug_format_setter(_Elems, _Ctx), ...); }, _Underlying);
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

return _Ctx.begin();
}
};

// formatter definition for all pairs and tuples, the deleted default constructor
// makes it "disabled" as per N4971 [format.formatter.spec]/5

template <class _TupleOrPair, class _CharT>
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;
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
};

template <class _CharT, formattable<_CharT> _Ty1, formattable<_CharT> _Ty2>
struct _Tuple_formatter_base<pair<_Ty1, _Ty2>, _CharT> : _Tuple_formatter_common_base<_CharT, _Ty1, _Ty2> {
private:
using _Formatted_type =
_Maybe_const<_Tuple_formatter_common_base<_CharT, _Ty1, _Ty2>::_Is_const_formattable, pair<_Ty1, _Ty2>>;
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

public:
template <class _FormatContext>
typename _FormatContext::iterator format(_Formatted_type& _Elems, _FormatContext& _Ctx) const {
return this->_Format(_Ctx, _Elems.first, _Elems.second);
}
};

template <class _CharT, formattable<_CharT>... _Types>
struct _Tuple_formatter_base<tuple<_Types...>, _CharT> : _Tuple_formatter_common_base<_CharT, _Types...> {
private:
using _Formatted_type =
_Maybe_const<_Tuple_formatter_common_base<_CharT, _Types...>::_Is_const_formattable, tuple<_Types...>>;

public:
template <class _FormatContext>
typename _FormatContext::iterator format(_Formatted_type& _Elems, _FormatContext& _Ctx) const {
return _STD apply([this, &_Ctx](auto&... _Args) { return this->_Format(_Ctx, _Args...); }, _Elems);
}
};

template <class _CharT, class _Ty1, class _Ty2>
struct formatter<pair<_Ty1, _Ty2>, _CharT> : _Tuple_formatter_base<pair<_Ty1, _Ty2>, _CharT> {};

template <class _CharT, class... _Types>
struct formatter<tuple<_Types...>, _CharT> : _Tuple_formatter_base<tuple<_Types...>, _CharT> {};

enum class _Add_newline : bool { _Nope, _Yes };

_NODISCARD inline string _Unescape_braces(const _Add_newline _Add_nl, const string_view _Old_str) {
Expand Down Expand Up @@ -4004,16 +4261,6 @@ _NODISCARD inline string _Unescape_braces(const _Add_newline _Add_nl, const stri
return _Unescaped_str;
}

template <class _CharT>
struct _Fill_align_and_width_specs { // used by thread::id and stacktrace_entry formatters
int _Width = -1;
int _Dynamic_width_index = -1;
_Fmt_align _Alignment = _Fmt_align::_None;
uint8_t _Fill_length = 1;
// At most one codepoint (so one char32_t or four utf-8 char8_t).
_CharT _Fill[4 / sizeof(_CharT)] = {' '};
};

template <class _CharT>
class _Fill_align_and_width_specs_setter {
public:
Expand Down
14 changes: 6 additions & 8 deletions tests/libcxx/expected_results.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_mov
# LLVM-79783: [libc++][test] array/size_and_alignment.compile.pass.cpp includes non-Standard <__type_traits/datasizeof.h>
std/containers/sequences/array/size_and_alignment.compile.pass.cpp FAIL

# LLVM-83734: [libc++][test] Don't include test_format_context.h in parse.pass.cpp
std/containers/sequences/vector.bool/vector.bool.fmt/parse.pass.cpp FAIL
std/thread/thread.threads/thread.thread.class/thread.thread.id/parse.pass.cpp FAIL
std/utilities/format/format.tuple/parse.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
Expand Down Expand Up @@ -259,7 +264,6 @@ std/containers/container.adaptors/container.adaptors.format/types.compile.pass.c
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/containers/sequences/vector.bool/vector.bool.fmt/parse.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
Expand All @@ -285,12 +289,6 @@ std/utilities/format/format.range/format.range.formatter/parse.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.formatter/underlying.pass.cpp FAIL
std/utilities/format/format.tuple/format.functions.format.pass.cpp FAIL
std/utilities/format/format.tuple/format.functions.vformat.pass.cpp FAIL
std/utilities/format/format.tuple/format.pass.cpp FAIL
std/utilities/format/format.tuple/parse.pass.cpp FAIL
std/utilities/format/format.tuple/set_brackets.pass.cpp FAIL
std/utilities/format/format.tuple/set_separator.pass.cpp FAIL
std/utilities/format/types.compile.pass.cpp FAIL

# P2363R5 Extending Associative Containers With The Remaining Heterogeneous Overloads
Expand Down Expand Up @@ -1115,7 +1113,6 @@ std/input.output/string.streams/stringstream/stringstream.members/str.allocator_
# says "Please create a vendor specific version of the test functions".
# If we do, some of these tests should be able to work.
std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp FAIL
std/thread/thread.threads/thread.thread.class/thread.thread.id/parse.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
std/utilities/format/format.formatter/format.context/format.context/ctor.pass.cpp FAIL
Expand All @@ -1133,6 +1130,7 @@ std/utilities/format/format.formatter/format.formatter.spec/formatter.pointer.pa
std/utilities/format/format.formatter/format.formatter.spec/formatter.signed_integral.pass.cpp FAIL
std/utilities/format/format.formatter/format.formatter.spec/formatter.string.pass.cpp FAIL
std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.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.
# warning C28020: The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ void test_P2286_vector_bool() {
// Tests for P2286 Formatting ranges
template <class CharT>
void test_P2286() {
assert_is_formattable<pair<int, int>, CharT>();
assert_is_formattable<tuple<int>, CharT>();

test_P2286_vector_bool<CharT, vector<bool>>();
test_P2286_vector_bool<CharT, pmr::vector<bool>>();
test_P2286_vector_bool<CharT, vector<bool, alternative_allocator<bool>>>();
Expand Down