Skip to content

Commit 22e72e1

Browse files
committed
Implement debug-enabled formatter specializations
* This PR implements debug-enabled standard `formatter` specializations, * Drive-by: rename tests added in microsoft#3656 - use `text_formatting_` prefix instead of `formatting_ranges_` (for consistency), * Towards microsoft#2919.
1 parent 0fe653a commit 22e72e1

File tree

11 files changed

+253
-3
lines changed

11 files changed

+253
-3
lines changed

stl/inc/format

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,18 @@ static_assert(static_cast<int>(_Basic_format_arg_type::_Custom_type) < 16, "must
125125
_NODISCARD constexpr bool _Is_integral_fmt_type(_Basic_format_arg_type _Ty) {
126126
return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_Char_type;
127127
}
128+
128129
_NODISCARD constexpr bool _Is_arithmetic_fmt_type(_Basic_format_arg_type _Ty) {
129130
return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_Long_double_type;
130131
}
131132

133+
#if _HAS_CXX23
134+
_NODISCARD constexpr bool _Is_debug_enabled_fmt_type(_Basic_format_arg_type _Ty) {
135+
return (_Ty == _Basic_format_arg_type::_Char_type) || (_Ty == _Basic_format_arg_type::_CString_type)
136+
|| (_Ty == _Basic_format_arg_type::_String_type);
137+
}
138+
#endif // _HAS_CXX23
139+
132140
struct _Auto_id_tag {};
133141

134142
// clang-format off
@@ -3554,8 +3562,18 @@ struct formatter {
35543562
_FMT_P2286_BEGIN
35553563
template <class _Ty, class _CharT, _Basic_format_arg_type _ArgType>
35563564
struct _Formatter_base {
3565+
private:
35573566
using _Pc = basic_format_parse_context<_CharT>;
35583567

3568+
public:
3569+
#if _HAS_CXX23
3570+
constexpr void set_debug_format() noexcept
3571+
requires (_Is_debug_enabled_fmt_type(_ArgType))
3572+
{
3573+
_Specs._Type = '?';
3574+
}
3575+
#endif // _HAS_CXX23
3576+
35593577
constexpr _Pc::iterator parse(_Pc& _ParseCtx) {
35603578
_Specs_checker<_Dynamic_specs_handler<_Pc>> _Handler(_Dynamic_specs_handler<_Pc>{_Specs, _ParseCtx}, _ArgType);
35613579
const auto _It = _Parse_format_specs(_ParseCtx._Unchecked_begin(), _ParseCtx._Unchecked_end(), _Handler);

tests/std/include/test_format_support.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,29 @@ struct choose_literal<char> {
1818
static constexpr const char* choose(const char* s, const wchar_t*) {
1919
return s;
2020
}
21+
22+
static constexpr char choose(char c, wchar_t) {
23+
return c;
24+
}
25+
26+
static constexpr std::string_view choose(std::string_view sv, std::wstring_view) {
27+
return sv;
28+
}
2129
};
2230

2331
template <>
2432
struct choose_literal<wchar_t> {
2533
static constexpr const wchar_t* choose(const char*, const wchar_t* s) {
2634
return s;
2735
}
36+
37+
static constexpr wchar_t choose(char, wchar_t c) {
38+
return c;
39+
}
40+
41+
static constexpr std::wstring_view choose(std::string_view, std::wstring_view sv) {
42+
return sv;
43+
}
2844
};
2945

3046
#define TYPED_LITERAL(CharT, Literal) (choose_literal<CharT>::choose(Literal, L##Literal))

tests/std/test.lst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -581,9 +581,10 @@ tests\P2278R4_const_span
581581
tests\P2278R4_ranges_const_iterator_machinery
582582
tests\P2278R4_ranges_const_range_machinery
583583
tests\P2278R4_views_as_const
584-
tests\P2286R8_formatting_ranges
585-
tests\P2286R8_formatting_ranges_legacy_text_encoding
586-
tests\P2286R8_formatting_ranges_utf8
584+
tests\P2286R8_text_formatting_debug_enabled_specializations
585+
tests\P2286R8_text_formatting_escaping
586+
tests\P2286R8_text_formatting_escaping_legacy_text_encoding
587+
tests\P2286R8_text_formatting_escaping_utf8
587588
tests\P2302R4_ranges_alg_contains
588589
tests\P2302R4_ranges_alg_contains_subrange
589590
tests\P2321R2_proxy_reference
File renamed without changes.
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#include <algorithm>
5+
#include <cassert>
6+
#include <concepts>
7+
#include <cstddef>
8+
#include <format>
9+
#include <memory_resource>
10+
#include <string>
11+
#include <string_view>
12+
#include <type_traits>
13+
#include <utility>
14+
15+
#include <test_format_support.hpp>
16+
17+
#define STR(Str) TYPED_LITERAL(CharT, Str)
18+
19+
using namespace std;
20+
21+
template <class F>
22+
concept DebugEnabledSpecialization = is_default_constructible_v<F> && requires(F& fmt) {
23+
{ fmt.set_debug_format() } noexcept -> same_as<void>;
24+
};
25+
26+
template <class CharT>
27+
consteval bool check_debug_enabled_specializations() {
28+
static_assert(DebugEnabledSpecialization<formatter<char, CharT>>);
29+
static_assert(DebugEnabledSpecialization<formatter<CharT, CharT>>);
30+
static_assert(DebugEnabledSpecialization<formatter<CharT*, CharT>>);
31+
static_assert(DebugEnabledSpecialization<formatter<const CharT*, CharT>>);
32+
static_assert(DebugEnabledSpecialization<formatter<CharT[3], CharT>>);
33+
static_assert(DebugEnabledSpecialization<formatter<basic_string<CharT>, CharT>>);
34+
static_assert(DebugEnabledSpecialization<formatter<pmr::basic_string<CharT>, CharT>>);
35+
static_assert(DebugEnabledSpecialization<formatter<basic_string_view<CharT>, CharT>>);
36+
37+
static_assert(!DebugEnabledSpecialization<formatter<signed char, CharT>>);
38+
static_assert(!DebugEnabledSpecialization<formatter<short, CharT>>);
39+
static_assert(!DebugEnabledSpecialization<formatter<int, CharT>>);
40+
static_assert(!DebugEnabledSpecialization<formatter<long, CharT>>);
41+
static_assert(!DebugEnabledSpecialization<formatter<long long, CharT>>);
42+
43+
static_assert(!DebugEnabledSpecialization<formatter<unsigned char, CharT>>);
44+
// NB: formatter<unsigned short, CharT> is special case, see below
45+
static_assert(!DebugEnabledSpecialization<formatter<unsigned int, CharT>>);
46+
static_assert(!DebugEnabledSpecialization<formatter<unsigned long, CharT>>);
47+
static_assert(!DebugEnabledSpecialization<formatter<unsigned long long, CharT>>);
48+
49+
static_assert(!DebugEnabledSpecialization<formatter<bool, CharT>>);
50+
static_assert(!DebugEnabledSpecialization<formatter<nullptr_t, CharT>>);
51+
static_assert(!DebugEnabledSpecialization<formatter<void*, CharT>>);
52+
static_assert(!DebugEnabledSpecialization<formatter<const void*, CharT>>);
53+
54+
// NB: wchar_t might be defined as a typedef for unsigned short (with '/Zc:wchar_t-')
55+
static_assert(!DebugEnabledSpecialization<formatter<unsigned short, CharT>>
56+
== (same_as<CharT, char> || !same_as<CharT, unsigned short>) );
57+
58+
return true;
59+
}
60+
61+
template <class CharT>
62+
struct Holder {
63+
char narrow_ch;
64+
CharT ch;
65+
const CharT* const_ptr;
66+
basic_string<CharT> str;
67+
CharT* non_const_ptr;
68+
basic_string_view<CharT> str_view;
69+
CharT arr[11];
70+
};
71+
72+
// holder-format-specs:
73+
// member debug-format(opt)
74+
// member:
75+
// 0 1 2 ... N (index of member object, single digit)
76+
// debug-format:
77+
// $ (use debug format)
78+
template <class CharT>
79+
struct std::formatter<Holder<CharT>, CharT> {
80+
public:
81+
constexpr formatter() : member_index{-1} {}
82+
83+
constexpr auto parse(basic_format_parse_context<CharT>& ctx) {
84+
auto it = ctx.begin();
85+
if (it == ctx.end() || *it == STR('}')) {
86+
throw format_error{"Invalid holder-format-specs."};
87+
}
88+
89+
if (STR('0') <= *it && *it <= STR('9')) {
90+
member_index = *it - STR('0');
91+
} else {
92+
throw format_error{"Expected member index in holder-format-specs."};
93+
}
94+
95+
++it;
96+
if (it == ctx.end() || *it == STR('}')) {
97+
return it;
98+
}
99+
100+
if (*it == '$') {
101+
switch (member_index) {
102+
case 0:
103+
fmt0.set_debug_format();
104+
break;
105+
106+
case 1:
107+
fmt1.set_debug_format();
108+
break;
109+
110+
case 2:
111+
fmt2.set_debug_format();
112+
break;
113+
114+
case 3:
115+
fmt3.set_debug_format();
116+
break;
117+
118+
case 4:
119+
fmt4.set_debug_format();
120+
break;
121+
122+
case 5:
123+
fmt5.set_debug_format();
124+
break;
125+
126+
case 6:
127+
fmt6.set_debug_format();
128+
break;
129+
}
130+
} else {
131+
throw format_error{"Unexpected symbols in holder-format-specs."};
132+
}
133+
134+
++it;
135+
if (it != ctx.end() && *it != STR('}')) {
136+
throw format_error{"Expected '}' at the end of holder-format-specs."};
137+
}
138+
139+
return it;
140+
}
141+
142+
template <class FormatContext>
143+
auto format(const Holder<CharT>& val, FormatContext& ctx) const {
144+
switch (member_index) {
145+
case 0:
146+
return fmt0.format(val.narrow_ch, ctx);
147+
case 1:
148+
return fmt1.format(val.ch, ctx);
149+
case 2:
150+
return fmt2.format(val.const_ptr, ctx);
151+
case 3:
152+
return fmt3.format(val.str, ctx);
153+
case 4:
154+
return fmt4.format(val.non_const_ptr, ctx);
155+
case 5:
156+
return fmt5.format(val.str_view, ctx);
157+
case 6:
158+
return fmt6.format(val.arr, ctx);
159+
}
160+
161+
unreachable();
162+
}
163+
164+
private:
165+
int member_index;
166+
167+
formatter<char, CharT> fmt0;
168+
formatter<CharT, CharT> fmt1;
169+
formatter<const CharT*, CharT> fmt2;
170+
formatter<basic_string<CharT>, CharT> fmt3;
171+
formatter<CharT*, CharT> fmt4;
172+
formatter<basic_string_view<CharT>, CharT> fmt5;
173+
formatter<CharT[11], CharT> fmt6;
174+
};
175+
176+
template <class CharT>
177+
void check_set_debug_format_function() {
178+
Holder<CharT> val;
179+
180+
val.narrow_ch = '\t';
181+
val.ch = STR('\t');
182+
val.const_ptr = STR("const\tCharT\t*");
183+
val.str = STR("basic\tstring");
184+
val.non_const_ptr = val.str.data();
185+
val.str_view = STR("basic\tstring\tview");
186+
ranges::copy(STR("CharT\t[11]\0"sv), val.arr);
187+
188+
assert(format(STR("{:0}"), val) == STR("\t"));
189+
assert(format(STR("{:1}"), val) == STR("\t"));
190+
assert(format(STR("{:2}"), val) == STR("const\tCharT\t*"));
191+
assert(format(STR("{:3}"), val) == STR("basic\tstring"));
192+
assert(format(STR("{:4}"), val) == STR("basic\tstring"));
193+
assert(format(STR("{:5}"), val) == STR("basic\tstring\tview"));
194+
assert(format(STR("{:6}"), val) == STR("CharT\t[11]"));
195+
196+
assert(format(STR("{:0$}"), val) == STR(R"('\t')"));
197+
assert(format(STR("{:1$}"), val) == STR(R"('\t')"));
198+
assert(format(STR("{:2$}"), val) == STR(R"("const\tCharT\t*")"));
199+
assert(format(STR("{:3$}"), val) == STR(R"("basic\tstring")"));
200+
assert(format(STR("{:4$}"), val) == STR(R"("basic\tstring")"));
201+
assert(format(STR("{:5$}"), val) == STR(R"("basic\tstring\tview")"));
202+
assert(format(STR("{:6$}"), val) == STR(R"("CharT\t[11]")"));
203+
}
204+
205+
int main() {
206+
static_assert(check_debug_enabled_specializations<char>());
207+
static_assert(check_debug_enabled_specializations<wchar_t>());
208+
209+
check_set_debug_format_function<char>();
210+
check_set_debug_format_function<wchar_t>();
211+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
RUNALL_INCLUDE ..\concepts_latest_matrix.lst
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)