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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -581,9 +581,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