xref: /llvm-project/libcxx/test/support/format.functions.common.h (revision 6a54dfbfe534276d644d7f9c027f0deeb748dd53)
1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef TEST_SUPPORT_FORMAT_FUNCTIONS_COMMON_H
10 #define TEST_SUPPORT_FORMAT_FUNCTIONS_COMMON_H
11 
12 // Contains the common part of the formatter tests for different papers.
13 
14 #include <algorithm>
15 #include <cctype>
16 #include <charconv>
17 #include <cstddef>
18 #include <cstdlib>
19 #include <format>
20 #include <ranges>
21 #include <string>
22 #include <string_view>
23 #include <vector>
24 
25 #include "make_string.h"
26 
27 #define STR(S) MAKE_STRING(CharT, S)
28 #define SV(S) MAKE_STRING_VIEW(CharT, S)
29 #define CSTR(S) MAKE_CSTRING(CharT, S)
30 
31 template <class T>
32 struct context {};
33 
34 template <>
35 struct context<char> {
36   using type = std::format_context;
37 };
38 
39 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
40 template <>
41 struct context<wchar_t> {
42   using type = std::wformat_context;
43 };
44 #endif
45 
46 template <class T>
47 using context_t = typename context<T>::type;
48 
49 // A user-defined type used to test the handle formatter.
50 enum class status : std::uint16_t { foo = 0xAAAA, bar = 0x5555, foobar = 0xAA55 };
51 
52 // The formatter for a user-defined type used to test the handle formatter.
53 template <class CharT>
54 struct std::formatter<status, CharT> {
55   // During the 2023 Issaquah meeting LEWG made it clear a formatter is
56   // required to call its parse function. LWG3892 Adds the wording for that
57   // requirement. Therefore this formatter is initialized in an invalid state.
58   // A call to parse sets it in a valid state and a call to format validates
59   // the state.
60   int type = -1;
61 
62   constexpr auto parse(basic_format_parse_context<CharT>& parse_ctx) -> decltype(parse_ctx.begin()) {
63     auto begin = parse_ctx.begin();
64     auto end   = parse_ctx.end();
65     type       = 0;
66     if (begin == end)
67       return begin;
68 
69     switch (*begin) {
70     case CharT('x'):
71       break;
72     case CharT('X'):
73       type = 1;
74       break;
75     case CharT('s'):
76       type = 2;
77       break;
78     case CharT('}'):
79       return begin;
80     default:
81       throw_format_error("The type option contains an invalid value for a status formatting argument");
82     }
83 
84     ++begin;
85     if (begin != end && *begin != CharT('}'))
86       throw_format_error("The format specifier should consume the input or end with a '}'");
87 
88     return begin;
89   }
90 
91   template <class Out>
92   auto format(status s, basic_format_context<Out, CharT>& ctx) const -> decltype(ctx.out()) {
93     const char* names[] = {"foo", "bar", "foobar"};
94     char buffer[7];
95     const char* begin = names[0];
96     const char* end = names[0];
97     switch (type) {
98     case -1:
99       throw_format_error("The formatter's parse function has not been called.");
100 
101     case 0:
102       begin = buffer;
103       buffer[0] = '0';
104       buffer[1] = 'x';
105       end = std::to_chars(&buffer[2], std::end(buffer), static_cast<std::uint16_t>(s), 16).ptr;
106       buffer[6] = '\0';
107       break;
108 
109     case 1:
110       begin = buffer;
111       buffer[0] = '0';
112       buffer[1] = 'X';
113       end = std::to_chars(&buffer[2], std::end(buffer), static_cast<std::uint16_t>(s), 16).ptr;
114       std::transform(static_cast<const char*>(&buffer[2]), end, &buffer[2], [](char c) {
115         return static_cast<char>(std::toupper(c)); });
116       buffer[6] = '\0';
117       break;
118 
119     case 2:
120       switch (s) {
121       case status::foo:
122         begin = names[0];
123         break;
124       case status::bar:
125         begin = names[1];
126         break;
127       case status::foobar:
128         begin = names[2];
129         break;
130       }
131       end = begin + strlen(begin);
132       break;
133     }
134 
135     return std::copy(begin, end, ctx.out());
136   }
137 
138 private:
139   [[noreturn]] void throw_format_error([[maybe_unused]] const char* s) const {
140 #ifndef TEST_HAS_NO_EXCEPTIONS
141     throw std::format_error(s);
142 #else
143     std::abort();
144 #endif
145   }
146 };
147 
148 struct parse_call_validator {
149   struct parse_function_not_called {};
150 
151   friend constexpr auto operator<=>(const parse_call_validator& lhs, const parse_call_validator& rhs) {
152     return &lhs <=> &rhs;
153   }
154 };
155 
156 // The formatter for a user-defined type used to test the handle formatter.
157 //
158 // Like std::formatter<status, CharT> this formatter validates that parse is
159 // called. This formatter is intended to be used when the formatter's parse is
160 // called directly and not with format. In that case the format-spec does not
161 // require a terminating }. The tests must be written in a fashion where this
162 // formatter is always called with an empty format-spec. This requirement
163 // allows testing of certain code paths that are never reached by using a
164 // well-formed format-string in the format functions.
165 template <class CharT>
166 struct std::formatter<parse_call_validator, CharT> {
167   bool parse_called{false};
168 
169   constexpr auto parse(basic_format_parse_context<CharT>& parse_ctx) -> decltype(parse_ctx.begin()) {
170     auto begin = parse_ctx.begin();
171     auto end   = parse_ctx.end();
172     assert(begin == end);
173     parse_called = true;
174     return begin;
175   }
176 
177   auto format(parse_call_validator, auto& ctx) const -> decltype(ctx.out()) {
178     if (!parse_called)
179       throw_error<parse_call_validator::parse_function_not_called>();
180     return ctx.out();
181   }
182 
183 private:
184   template <class T>
185   [[noreturn]] void throw_error() const {
186 #ifndef TEST_HAS_NO_EXCEPTIONS
187     throw T{};
188 #else
189     std::abort();
190 #endif
191   }
192 };
193 
194 // Creates format string for the invalid types.
195 //
196 // valid contains a list of types that are valid.
197 // - The type ?s is the only type requiring 2 characters, use S for that type.
198 // - Whether n is a type or not depends on the context, is is always used.
199 //
200 // The return value is a collection of basic_strings, instead of
201 // basic_string_views since the values are temporaries.
202 namespace detail {
203 template <class CharT, std::size_t N>
204 std::basic_string<CharT> get_colons() {
205   static std::basic_string<CharT> result(N, CharT(':'));
206   return result;
207 }
208 
209 constexpr std::string_view get_format_types() {
210   return "aAbBcdeEfFgGopPsxX"
211 #if TEST_STD_VER > 20
212          "?"
213 #endif
214       ;
215 }
216 
217 template <class CharT, /*format_types types,*/ size_t N>
218 std::vector<std::basic_string<CharT>> fmt_invalid_types(std::string_view valid) {
219   // std::ranges::to is not available in C++20.
220   std::vector<std::basic_string<CharT>> result;
221   std::ranges::copy(
222       get_format_types() | std::views::filter([&](char type) { return valid.find(type) == std::string_view::npos; }) |
223           std::views::transform([&](char type) { return std::format(SV("{{{}{}}}"), get_colons<CharT, N>(), type); }),
224       std::back_inserter(result));
225   return result;
226 }
227 
228 } // namespace detail
229 
230 // Creates format string for the invalid types.
231 //
232 // valid contains a list of types that are valid.
233 //
234 // The return value is a collection of basic_strings, instead of
235 // basic_string_views since the values are temporaries.
236 template <class CharT>
237 std::vector<std::basic_string<CharT>> fmt_invalid_types(std::string_view valid) {
238   return detail::fmt_invalid_types<CharT, 1>(valid);
239 }
240 
241 // Like fmt_invalid_types but when the format spec is for an underlying formatter.
242 template <class CharT>
243 std::vector<std::basic_string<CharT>> fmt_invalid_nested_types(std::string_view valid) {
244   return detail::fmt_invalid_types<CharT, 2>(valid);
245 }
246 
247 #endif // TEST_SUPPORT_FORMAT_FUNCTIONS_COMMON_H
248