Skip to content

Commit

Permalink
Implement debug-enabled formatter specializations (#3913)
Browse files Browse the repository at this point in the history
Co-authored-by: Stephan T. Lavavej <[email protected]>
  • Loading branch information
JMazurkiewicz and StephanTLavavej authored Sep 21, 2023
1 parent f6580c2 commit ddebb7b
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 10 deletions.
77 changes: 70 additions & 7 deletions stl/inc/format
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,18 @@ static_assert(static_cast<int>(_Basic_format_arg_type::_Custom_type) < 16, "must
_NODISCARD constexpr bool _Is_integral_fmt_type(_Basic_format_arg_type _Ty) {
return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_Char_type;
}

_NODISCARD constexpr bool _Is_arithmetic_fmt_type(_Basic_format_arg_type _Ty) {
return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_Long_double_type;
}

#if _HAS_CXX23
_NODISCARD consteval bool _Is_debug_enabled_fmt_type(_Basic_format_arg_type _Ty) {
return _Ty == _Basic_format_arg_type::_Char_type || _Ty == _Basic_format_arg_type::_CString_type
|| _Ty == _Basic_format_arg_type::_String_type;
}
#endif // _HAS_CXX23

struct _Auto_id_tag {
explicit _Auto_id_tag() = default;
};
Expand Down Expand Up @@ -3580,8 +3588,18 @@ struct formatter {
_FMT_P2286_BEGIN
template <class _Ty, class _CharT, _Basic_format_arg_type _ArgType>
struct _Formatter_base {
private:
using _Pc = basic_format_parse_context<_CharT>;

public:
#if _HAS_CXX23
constexpr void _Set_debug_format() noexcept
requires (_Is_debug_enabled_fmt_type(_ArgType))
{
_Specs._Type = '?';
}
#endif // _HAS_CXX23

constexpr _Pc::iterator parse(_Pc& _ParseCtx) {
_Specs_checker<_Dynamic_specs_handler<_Pc>> _Handler(_Dynamic_specs_handler<_Pc>{_Specs, _ParseCtx}, _ArgType);
const auto _It = _Parse_format_specs(_ParseCtx._Unchecked_begin(), _ParseCtx._Unchecked_end(), _Handler);
Expand Down Expand Up @@ -3634,35 +3652,80 @@ _FORMAT_SPECIALIZE_FOR(short, _Basic_format_arg_type::_Int_type);
_FORMAT_SPECIALIZE_FOR(unsigned short, _Basic_format_arg_type::_UInt_type);
_FORMAT_SPECIALIZE_FOR(long, _Basic_format_arg_type::_Int_type);
_FORMAT_SPECIALIZE_FOR(unsigned long, _Basic_format_arg_type::_UInt_type);
_FORMAT_SPECIALIZE_FOR(char, _Basic_format_arg_type::_Char_type);
_FORMAT_SPECIALIZE_FOR(signed char, _Basic_format_arg_type::_Int_type);
_FORMAT_SPECIALIZE_FOR(unsigned char, _Basic_format_arg_type::_UInt_type);

#undef _FORMAT_SPECIALIZE_FOR

// not using the macro because we'd like to add 'set_debug_format' member function in C++23 mode
template <_Format_supported_charT _CharT>
struct formatter<char, _CharT> : _Formatter_base<char, _CharT, _Basic_format_arg_type::_Char_type> {
#if _HAS_CXX23
constexpr void set_debug_format() noexcept {
this->_Set_debug_format();
}
#endif // _HAS_CXX23
};

// not using the macro because we'd like to avoid the formatter<wchar_t, char> specialization
template <>
struct formatter<wchar_t, wchar_t> : _Formatter_base<wchar_t, wchar_t, _Basic_format_arg_type::_Char_type> {};
struct formatter<wchar_t, wchar_t> : _Formatter_base<wchar_t, wchar_t, _Basic_format_arg_type::_Char_type> {
#if _HAS_CXX23
constexpr void set_debug_format() noexcept {
_Set_debug_format();
}
#endif // _HAS_CXX23
};

// We could use the macro for these specializations, but it's confusing to refer to symbols that are defined
// inside the macro in the macro's "call".
template <_Format_supported_charT _CharT>
struct formatter<_CharT*, _CharT> : _Formatter_base<_CharT*, _CharT, _Basic_format_arg_type::_CString_type> {};
struct formatter<_CharT*, _CharT> : _Formatter_base<_CharT*, _CharT, _Basic_format_arg_type::_CString_type> {
#if _HAS_CXX23
constexpr void set_debug_format() noexcept {
this->_Set_debug_format();
}
#endif // _HAS_CXX23
};

template <_Format_supported_charT _CharT>
struct formatter<const _CharT*, _CharT>
: _Formatter_base<const _CharT*, _CharT, _Basic_format_arg_type::_CString_type> {};
: _Formatter_base<const _CharT*, _CharT, _Basic_format_arg_type::_CString_type> {
#if _HAS_CXX23
constexpr void set_debug_format() noexcept {
this->_Set_debug_format();
}
#endif // _HAS_CXX23
};

template <_Format_supported_charT _CharT, size_t _Nx>
struct formatter<_CharT[_Nx], _CharT> : _Formatter_base<_CharT[_Nx], _CharT, _Basic_format_arg_type::_CString_type> {};
struct formatter<_CharT[_Nx], _CharT> : _Formatter_base<_CharT[_Nx], _CharT, _Basic_format_arg_type::_CString_type> {
#if _HAS_CXX23
constexpr void set_debug_format() noexcept {
this->_Set_debug_format();
}
#endif // _HAS_CXX23
};

template <_Format_supported_charT _CharT, class _Traits, class _Allocator>
struct formatter<basic_string<_CharT, _Traits, _Allocator>, _CharT>
: _Formatter_base<basic_string<_CharT, _Traits, _Allocator>, _CharT, _Basic_format_arg_type::_String_type> {};
: _Formatter_base<basic_string<_CharT, _Traits, _Allocator>, _CharT, _Basic_format_arg_type::_String_type> {
#if _HAS_CXX23
constexpr void set_debug_format() noexcept {
this->_Set_debug_format();
}
#endif // _HAS_CXX23
};

template <_Format_supported_charT _CharT, class _Traits>
struct formatter<basic_string_view<_CharT, _Traits>, _CharT>
: _Formatter_base<basic_string_view<_CharT, _Traits>, _CharT, _Basic_format_arg_type::_String_type> {};
: _Formatter_base<basic_string_view<_CharT, _Traits>, _CharT, _Basic_format_arg_type::_String_type> {
#if _HAS_CXX23
constexpr void set_debug_format() noexcept {
this->_Set_debug_format();
}
#endif // _HAS_CXX23
};

_EXPORT_STD template <class _CharT, class... _Args>
struct basic_format_string {
Expand Down
8 changes: 8 additions & 0 deletions tests/std/include/test_format_support.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ struct choose_literal<char> {
static constexpr char choose(char c, wchar_t) {
return c;
}

static constexpr std::string_view choose(std::string_view sv, std::wstring_view) {
return sv;
}
};

template <>
Expand All @@ -37,6 +41,10 @@ struct choose_literal<wchar_t> {
static constexpr wchar_t choose(char, wchar_t c) {
return c;
}

static constexpr std::wstring_view choose(std::string_view, std::wstring_view sv) {
return sv;
}
};

#define TYPED_LITERAL(CharT, Literal) (choose_literal<CharT>::choose(Literal, L##Literal))
Expand Down
7 changes: 4 additions & 3 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,10 @@ tests\P2278R4_const_span
tests\P2278R4_ranges_const_iterator_machinery
tests\P2278R4_ranges_const_range_machinery
tests\P2278R4_views_as_const
tests\P2286R8_formatting_ranges
tests\P2286R8_formatting_ranges_legacy_text_encoding
tests\P2286R8_formatting_ranges_utf8
tests\P2286R8_text_formatting_debug_enabled_specializations
tests\P2286R8_text_formatting_escaping
tests\P2286R8_text_formatting_escaping_legacy_text_encoding
tests\P2286R8_text_formatting_escaping_utf8
tests\P2302R4_ranges_alg_contains
tests\P2302R4_ranges_alg_contains_subrange
tests\P2321R2_proxy_reference
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <cassert>
#include <concepts>
#include <cstddef>
#include <format>
#include <memory_resource>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>

#include <test_format_support.hpp>

#define STR(Str) TYPED_LITERAL(CharT, Str)

using namespace std;

template <class F>
concept DebugEnabledSpecialization = is_default_constructible_v<F> && requires(F& fmt) {
{ fmt.set_debug_format() } noexcept -> same_as<void>;
};

template <class CharT>
consteval bool check_debug_enabled_specializations() {
static_assert(DebugEnabledSpecialization<formatter<char, CharT>>);
static_assert(DebugEnabledSpecialization<formatter<CharT, CharT>>);
static_assert(DebugEnabledSpecialization<formatter<CharT*, CharT>>);
static_assert(DebugEnabledSpecialization<formatter<const CharT*, CharT>>);
static_assert(DebugEnabledSpecialization<formatter<CharT[3], CharT>>);
static_assert(DebugEnabledSpecialization<formatter<basic_string<CharT>, CharT>>);
static_assert(DebugEnabledSpecialization<formatter<pmr::basic_string<CharT>, CharT>>);
static_assert(DebugEnabledSpecialization<formatter<basic_string_view<CharT>, CharT>>);

static_assert(!DebugEnabledSpecialization<formatter<signed char, CharT>>);
static_assert(!DebugEnabledSpecialization<formatter<short, CharT>>);
static_assert(!DebugEnabledSpecialization<formatter<int, CharT>>);
static_assert(!DebugEnabledSpecialization<formatter<long, CharT>>);
static_assert(!DebugEnabledSpecialization<formatter<long long, CharT>>);

static_assert(!DebugEnabledSpecialization<formatter<unsigned char, CharT>>);
// NB: formatter<unsigned short, CharT> is special case, see below
static_assert(!DebugEnabledSpecialization<formatter<unsigned int, CharT>>);
static_assert(!DebugEnabledSpecialization<formatter<unsigned long, CharT>>);
static_assert(!DebugEnabledSpecialization<formatter<unsigned long long, CharT>>);

static_assert(!DebugEnabledSpecialization<formatter<bool, CharT>>);
static_assert(!DebugEnabledSpecialization<formatter<nullptr_t, CharT>>);
static_assert(!DebugEnabledSpecialization<formatter<void*, CharT>>);
static_assert(!DebugEnabledSpecialization<formatter<const void*, CharT>>);

// NB: wchar_t might be defined as a typedef for unsigned short (with '/Zc:wchar_t-')
static_assert(DebugEnabledSpecialization<formatter<unsigned short, CharT>> == same_as<CharT, unsigned short>);

return true;
}

template <class CharT>
struct Holder {
char narrow_ch;
CharT ch;
const CharT* const_ptr;
basic_string<CharT> str;
CharT* non_const_ptr;
basic_string_view<CharT> str_view;
CharT arr[11];
};

// holder-format-specs:
// member debug-format(opt)
// member:
// 0 1 2 ... N (index of member object, single digit)
// debug-format:
// $ (use debug format)
template <class CharT>
struct std::formatter<Holder<CharT>, CharT> {
public:
constexpr auto parse(basic_format_parse_context<CharT>& ctx) {
auto it = ctx.begin();
if (it == ctx.end() || *it == STR('}')) {
throw format_error{"Invalid holder-format-specs."};
}

if (STR('0') <= *it && *it <= STR('9')) {
member_index = *it - STR('0');
} else {
throw format_error{"Expected member index in holder-format-specs."};
}

++it;
if (it == ctx.end() || *it == STR('}')) {
return it;
}

if (*it == '$') {
switch (member_index) {
case 0:
fmt0.set_debug_format();
break;
case 1:
fmt1.set_debug_format();
break;
case 2:
fmt2.set_debug_format();
break;
case 3:
fmt3.set_debug_format();
break;
case 4:
fmt4.set_debug_format();
break;
case 5:
fmt5.set_debug_format();
break;
case 6:
fmt6.set_debug_format();
break;
}
} else {
throw format_error{"Unexpected symbols in holder-format-specs."};
}

++it;
if (it != ctx.end() && *it != STR('}')) {
throw format_error{"Expected '}' at the end of holder-format-specs."};
}

return it;
}

template <class FormatContext>
auto format(const Holder<CharT>& val, FormatContext& ctx) const {
switch (member_index) {
case 0:
return fmt0.format(val.narrow_ch, ctx);
case 1:
return fmt1.format(val.ch, ctx);
case 2:
return fmt2.format(val.const_ptr, ctx);
case 3:
return fmt3.format(val.str, ctx);
case 4:
return fmt4.format(val.non_const_ptr, ctx);
case 5:
return fmt5.format(val.str_view, ctx);
case 6:
return fmt6.format(val.arr, ctx);
}

unreachable();
}

private:
int member_index{-1};

formatter<char, CharT> fmt0;
formatter<CharT, CharT> fmt1;
formatter<const CharT*, CharT> fmt2;
formatter<basic_string<CharT>, CharT> fmt3;
formatter<CharT*, CharT> fmt4;
formatter<basic_string_view<CharT>, CharT> fmt5;
formatter<CharT[11], CharT> fmt6;
};

template <class CharT>
void check_set_debug_format_function() {
Holder<CharT> val;

val.narrow_ch = '\t';
val.ch = STR('\t');
val.const_ptr = STR("const\tCharT\t*");
val.str = STR("basic\tstring");
val.non_const_ptr = val.str.data();
val.str_view = STR("basic\tstring\tview");
ranges::copy(STR("CharT\t[11]\0"sv), val.arr);

assert(format(STR("{:0}"), val) == STR("\t"));
assert(format(STR("{:1}"), val) == STR("\t"));
assert(format(STR("{:2}"), val) == STR("const\tCharT\t*"));
assert(format(STR("{:3}"), val) == STR("basic\tstring"));
assert(format(STR("{:4}"), val) == STR("basic\tstring"));
assert(format(STR("{:5}"), val) == STR("basic\tstring\tview"));
assert(format(STR("{:6}"), val) == STR("CharT\t[11]"));

assert(format(STR("{:0$}"), val) == STR(R"('\t')"));
assert(format(STR("{:1$}"), val) == STR(R"('\t')"));
assert(format(STR("{:2$}"), val) == STR(R"("const\tCharT\t*")"));
assert(format(STR("{:3$}"), val) == STR(R"("basic\tstring")"));
assert(format(STR("{:4$}"), val) == STR(R"("basic\tstring")"));
assert(format(STR("{:5$}"), val) == STR(R"("basic\tstring\tview")"));
assert(format(STR("{:6$}"), val) == STR(R"("CharT\t[11]")"));
}

void set_debug_format(auto&) {}

struct name_lookup_in_formatter_checker : formatter<int> {
auto parse(auto& ctx) { // COMPILE-ONLY
set_debug_format(*this);
return ctx.begin();
}
};

int main() {
static_assert(check_debug_enabled_specializations<char>());
static_assert(check_debug_enabled_specializations<wchar_t>());

check_set_debug_format_function<char>();
check_set_debug_format_function<wchar_t>();
}
4 changes: 4 additions & 0 deletions tests/std/tests/P2286R8_text_formatting_escaping/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_latest_matrix.lst

0 comments on commit ddebb7b

Please sign in to comment.