Hegel 0.11.4
Property-based testing for C++
Loading...
Searching...
No Matches
repr.h
1#pragma once
2
6
7// Renders drawn values as C++ expressions for draw-output lines. Types the
8// library cannot render as a compilable expression fall back to the text of
9// their stream operator, then to an "<unprintable Type>" placeholder.
10
11#include "config.h"
12
13#include <array>
14#include <cmath>
15#include <cstddef>
16#include <cstdio>
17#include <limits>
18#include <map>
19#include <optional>
20#include <set>
21#include <sstream>
22#include <string>
23#include <string_view>
24#include <tuple>
25#include <type_traits>
26#include <utility>
27#include <variant>
28#include <vector>
29
30#if HEGEL_HAS_REFLECTION
31#include <rfl.hpp>
32#endif
33
34namespace hegel::internal {
35
36 template <typename T> std::string cpp_type_name();
37 template <typename T> std::string repr(const T& value);
38
39 // Best-effort type name sliced from the compiler's pretty function
40 // string. Used for placeholder output and for types with no clean
41 // spelling of their own.
42 template <typename T> constexpr std::string_view compiler_type_name() {
43#if defined(__clang__)
44 std::string_view p = __PRETTY_FUNCTION__;
45 auto start = p.find("T = ");
46 auto end = p.rfind(']');
47 if (start == std::string_view::npos || end == std::string_view::npos) {
48 return "?"; // GCOVR_EXCL_LINE - guards a compiler format change
49 }
50 start += 4;
51 return p.substr(start, end - start);
52#elif defined(__GNUC__)
53 std::string_view p = __PRETTY_FUNCTION__;
54 auto start = p.find("T = ");
55 if (start == std::string_view::npos) {
56 return "?";
57 }
58 start += 4;
59 auto end = p.find(';', start);
60 if (end == std::string_view::npos) {
61 end = p.rfind(']');
62 }
63 if (end == std::string_view::npos) {
64 return "?";
65 }
66 return p.substr(start, end - start);
67#elif defined(_MSC_VER)
68 std::string_view p = __FUNCSIG__;
69 constexpr std::string_view marker = "compiler_type_name<";
70 auto start = p.find(marker);
71 auto end = p.rfind(">(");
72 if (start == std::string_view::npos || end == std::string_view::npos) {
73 return "?";
74 }
75 start += marker.size();
76 return p.substr(start, end - start);
77#else
78 return "?";
79#endif
80 }
81
82 namespace repr_detail {
83
84 template <typename T> struct is_vector : std::false_type {};
85 template <typename T>
86 struct is_vector<std::vector<T>> : std::true_type {};
87
88 template <typename T> struct is_set : std::false_type {};
89 template <typename T> struct is_set<std::set<T>> : std::true_type {};
90
91 template <typename T> struct is_map : std::false_type {};
92 template <typename K, typename V>
93 struct is_map<std::map<K, V>> : std::true_type {};
94
95 template <typename T> struct is_optional : std::false_type {};
96 template <typename T>
97 struct is_optional<std::optional<T>> : std::true_type {};
98
99 template <typename T> struct is_pair : std::false_type {};
100 template <typename A, typename B>
101 struct is_pair<std::pair<A, B>> : std::true_type {};
102
103 template <typename T> struct is_tuple : std::false_type {};
104 template <typename... Ts>
105 struct is_tuple<std::tuple<Ts...>> : std::true_type {};
106
107 template <typename T> struct is_variant : std::false_type {};
108 template <typename... Ts>
109 struct is_variant<std::variant<Ts...>> : std::true_type {};
110
111 template <typename T> struct is_std_array : std::false_type {};
112 template <typename E, std::size_t N>
113 struct is_std_array<std::array<E, N>> : std::true_type {};
114
115 template <typename T> struct array_traits;
116 template <typename E, std::size_t N>
117 struct array_traits<std::array<E, N>> {
118 using element_type = E;
119 static constexpr std::size_t size = N;
120 };
121
122 template <typename T, typename = void>
123 struct has_ostream_operator : std::false_type {};
124 template <typename T>
125 struct has_ostream_operator<
126 T, std::void_t<decltype(std::declval<std::ostream&>()
127 << std::declval<const T&>())>>
128 : std::true_type {};
129
130#if HEGEL_HAS_REFLECTION
131 template <typename T, typename = void>
132 struct is_reflectable_impl : std::false_type {};
133 template <typename T>
134 struct is_reflectable_impl<
135 T, std::void_t<decltype(rfl::to_view(std::declval<T&>()))>>
136 : std::true_type {};
137
138 // The aggregate check must come first: rfl::to_view hard-errors on
139 // non-aggregates instead of failing substitution.
140 template <typename T>
141 struct is_reflectable
142 : std::conjunction<std::is_aggregate<T>, is_reflectable_impl<T>> {};
143#endif
144
145 template <typename... Ts> std::string type_name_list() {
146 if constexpr (sizeof...(Ts) == 0) {
147 return "";
148 } else {
149 std::string names[] = {cpp_type_name<Ts>()...};
150 std::string out;
151 for (std::size_t i = 0; i < sizeof...(Ts); ++i) {
152 if (i != 0) {
153 out += ", ";
154 }
155 out += names[i];
156 }
157 return out;
158 }
159 }
160
161 template <typename T> struct template_args;
162 template <typename... Ts> struct template_args<std::tuple<Ts...>> {
163 static std::string names() { return type_name_list<Ts...>(); }
164 };
165 template <typename... Ts> struct template_args<std::variant<Ts...>> {
166 static std::string names() { return type_name_list<Ts...>(); }
167 };
168
169 // Appends one byte as it must appear inside a char or string
170 // literal: named escapes, printable ASCII verbatim, all other
171 // bytes as a fixed three-digit octal escape (a following literal
172 // digit can never extend a three-digit octal escape).
173 inline void append_escaped(std::string& out, unsigned char c,
174 bool in_string) {
175 switch (c) {
176 case '\n':
177 out += "\\n";
178 return;
179 case '\t':
180 out += "\\t";
181 return;
182 case '\r':
183 out += "\\r";
184 return;
185 case '\\':
186 out += "\\\\";
187 return;
188 case '"':
189 if (in_string) {
190 out += "\\\"";
191 return;
192 }
193 break;
194 case '\'':
195 if (!in_string) {
196 out += "\\'";
197 return;
198 }
199 break;
200 default:
201 break;
202 }
203 if (c >= 0x20 && c < 0x7f) {
204 out += static_cast<char>(c);
205 return;
206 }
207 char buf[8];
208 std::snprintf(buf, sizeof(buf), "\\%03o", c);
209 out += buf;
210 }
211
212 } // namespace repr_detail
213
214 // C++ source spelling of T, for use inside rendered expressions.
215 template <typename T> std::string cpp_type_name() {
216 if constexpr (std::is_same_v<T, bool>) {
217 return "bool";
218 } else if constexpr (std::is_same_v<T, char>) {
219 return "char";
220 } else if constexpr (std::is_same_v<T, signed char>) {
221 return "signed char";
222 } else if constexpr (std::is_same_v<T, unsigned char>) {
223 return "unsigned char";
224 } else if constexpr (std::is_same_v<T, short>) {
225 return "short";
226 } else if constexpr (std::is_same_v<T, unsigned short>) {
227 return "unsigned short";
228 } else if constexpr (std::is_same_v<T, int>) {
229 return "int";
230 } else if constexpr (std::is_same_v<T, unsigned int>) {
231 return "unsigned int";
232 } else if constexpr (std::is_same_v<T, long>) {
233 return "long";
234 } else if constexpr (std::is_same_v<T, unsigned long>) {
235 return "unsigned long";
236 } else if constexpr (std::is_same_v<T, long long>) {
237 return "long long";
238 } else if constexpr (std::is_same_v<T, unsigned long long>) {
239 return "unsigned long long";
240 } else if constexpr (std::is_same_v<T, float>) {
241 return "float";
242 } else if constexpr (std::is_same_v<T, double>) {
243 return "double";
244 } else if constexpr (std::is_same_v<T, long double>) {
245 return "long double";
246 } else if constexpr (std::is_same_v<T, std::string>) {
247 return "std::string";
248 } else if constexpr (std::is_same_v<T, std::monostate>) {
249 return "std::monostate";
250 } else if constexpr (repr_detail::is_vector<T>::value) {
251 return "std::vector<" + cpp_type_name<typename T::value_type>() +
252 ">";
253 } else if constexpr (repr_detail::is_set<T>::value) {
254 return "std::set<" + cpp_type_name<typename T::value_type>() + ">";
255 } else if constexpr (repr_detail::is_map<T>::value) {
256 return "std::map<" + cpp_type_name<typename T::key_type>() + ", " +
257 cpp_type_name<typename T::mapped_type>() + ">";
258 } else if constexpr (repr_detail::is_optional<T>::value) {
259 return "std::optional<" + cpp_type_name<typename T::value_type>() +
260 ">";
261 } else if constexpr (repr_detail::is_pair<T>::value) {
262 return "std::pair<" + cpp_type_name<typename T::first_type>() +
263 ", " + cpp_type_name<typename T::second_type>() + ">";
264 } else if constexpr (repr_detail::is_tuple<T>::value) {
265 return "std::tuple<" + repr_detail::template_args<T>::names() + ">";
266 } else if constexpr (repr_detail::is_variant<T>::value) {
267 return "std::variant<" + repr_detail::template_args<T>::names() +
268 ">";
269 } else if constexpr (repr_detail::is_std_array<T>::value) {
270 using traits = repr_detail::array_traits<T>;
271 return "std::array<" +
272 cpp_type_name<typename traits::element_type>() + ", " +
273 std::to_string(traits::size) + ">";
274 } else {
275 return std::string(compiler_type_name<T>());
276 }
277 }
278
279 // Renders a value as one C++ expression, or as a pseudo-expression for
280 // types with no renderable form.
281 template <typename T> std::string repr(const T& value) {
282 if constexpr (std::is_same_v<T, bool>) {
283 return value ? "true" : "false";
284 } else if constexpr (std::is_same_v<T, char>) {
285 std::string out = "'";
286 repr_detail::append_escaped(out, static_cast<unsigned char>(value),
287 /*in_string=*/false);
288 out += "'";
289 return out;
290 } else if constexpr (std::is_same_v<T, signed char> ||
291 std::is_same_v<T, unsigned char>) {
292 return "static_cast<" + cpp_type_name<T>() + ">(" +
293 std::to_string(static_cast<int>(value)) + ")";
294 } else if constexpr (std::is_same_v<T, int>) {
295 return std::to_string(value);
296 } else if constexpr (std::is_same_v<T, unsigned int>) {
297 return std::to_string(value) + "u";
298 } else if constexpr (std::is_same_v<T, long>) {
299 return std::to_string(value) + "l";
300 } else if constexpr (std::is_same_v<T, unsigned long>) {
301 return std::to_string(value) + "ul";
302 } else if constexpr (std::is_same_v<T, long long>) {
303 return std::to_string(value) + "ll";
304 } else if constexpr (std::is_same_v<T, unsigned long long>) {
305 return std::to_string(value) + "ull";
306 } else if constexpr (std::is_integral_v<T>) {
307 // Integral types with no literal suffix of their own.
308 return "static_cast<" + cpp_type_name<T>() + ">(" +
309 std::to_string(value) + ")";
310 } else if constexpr (std::is_enum_v<T>) {
311 using underlying = std::underlying_type_t<T>;
312 return "static_cast<" + cpp_type_name<T>() + ">(" +
313 repr(static_cast<underlying>(value)) + ")";
314 } else if constexpr (std::is_floating_point_v<T>) {
315 if (std::isnan(value)) {
316 return "std::numeric_limits<" + cpp_type_name<T>() +
317 ">::quiet_NaN()";
318 }
319 if (std::isinf(value)) {
320 std::string inf = "std::numeric_limits<" + cpp_type_name<T>() +
321 ">::infinity()";
322 return value < 0 ? "-" + inf : inf;
323 }
324 char buf[64];
325 constexpr int digits = std::numeric_limits<T>::max_digits10;
326 if constexpr (std::is_same_v<T, long double>) {
327 std::snprintf(buf, sizeof(buf), "%.*Lg", digits, value);
328 } else {
329 std::snprintf(buf, sizeof(buf), "%.*g", digits,
330 static_cast<double>(value));
331 }
332 std::string out = buf;
333 // keep integral-valued float rendering as float since %g drops
334 // decimal when it isn't needed.
335 if (out.find('.') == std::string::npos &&
336 out.find('e') == std::string::npos &&
337 out.find('E') == std::string::npos) {
338 out += ".0";
339 }
340 if constexpr (std::is_same_v<T, float>) {
341 out += "f";
342 } else if constexpr (std::is_same_v<T, long double>) {
343 out += "L";
344 }
345 return out;
346 } else if constexpr (std::is_same_v<T, std::string>) {
347 bool has_nul = value.find('\0') != std::string::npos;
348 std::string out = "std::string(\"";
349 for (char c : value) {
350 repr_detail::append_escaped(out, static_cast<unsigned char>(c),
351 /*in_string=*/true);
352 }
353 out += "\"";
354 if (has_nul) {
355 // The pointer constructor would stop at the first NUL.
356 out += ", " + std::to_string(value.size());
357 }
358 out += ")";
359 return out;
360 } else if constexpr (std::is_same_v<T, std::monostate>) {
361 return "std::monostate{}";
362 } else if constexpr (repr_detail::is_pair<T>::value) {
363 return cpp_type_name<T>() + "{" + repr(value.first) + ", " +
364 repr(value.second) + "}";
365 } else if constexpr (repr_detail::is_tuple<T>::value) {
366 std::string out = cpp_type_name<T>() + "{";
367 std::apply(
368 [&out](const auto&... elems) {
369 std::size_t i = 0;
370 ((out +=
371 repr(elems) + (++i < sizeof...(elems) ? ", " : "")),
372 ...);
373 },
374 value);
375 out += "}";
376 return out;
377 } else if constexpr (repr_detail::is_optional<T>::value) {
378 std::string name = cpp_type_name<T>();
379 return value.has_value() ? name + "{" + repr(*value) + "}"
380 : name + "{}";
381 } else if constexpr (repr_detail::is_variant<T>::value) {
382 // in_place_index keeps variants with repeated alternative types
383 // unambiguous.
384 std::string out = cpp_type_name<T>() + "{std::in_place_index<" +
385 std::to_string(value.index()) + ">, ";
386 std::visit([&out](const auto& v) { out += repr(v); }, value);
387 out += "}";
388 return out;
389 } else if constexpr (repr_detail::is_vector<T>::value ||
390 repr_detail::is_set<T>::value ||
391 repr_detail::is_std_array<T>::value) {
392 std::string out = cpp_type_name<T>() + "{";
393 bool first = true;
394 for (const auto& element : value) {
395 if (!first) {
396 out += ", ";
397 }
398 first = false;
399 out += repr(element);
400 }
401 out += "}";
402 return out;
403 } else if constexpr (repr_detail::is_map<T>::value) {
404 std::string out = cpp_type_name<T>() + "{";
405 bool first = true;
406 for (const auto& entry : value) {
407 if (!first) {
408 out += ", ";
409 }
410 first = false;
411 out +=
412 "{" + repr(entry.first) + ", " + repr(entry.second) + "}";
413 }
414 out += "}";
415 return out;
416 }
417#if HEGEL_HAS_REFLECTION
418 else if constexpr (repr_detail::is_reflectable<T>::value) {
419 std::string out = cpp_type_name<T>() + "{";
420 auto view = rfl::to_view(const_cast<T&>(value));
421 bool first = true;
422 view.apply([&out, &first](const auto& field) {
423 if (!first) {
424 out += ", ";
425 }
426 first = false;
427 out += ".";
428 out += field.name();
429 out += " = ";
430 out += repr(*field.value());
431 });
432 out += "}";
433 return out;
434 }
435#endif
436 else if constexpr (repr_detail::has_ostream_operator<T>::value) {
437 std::ostringstream os;
438 os << value;
439 return os.str();
440 } else {
441 return "<unprintable " + std::string(compiler_type_name<T>()) + ">";
442 }
443 }
444
445} // namespace hegel::internal
446
Generator< std::variant< Ts... > > variant(Generator< Ts >... gens)
Generate std::variant from heterogeneous generators.
Definition combinators.h:235
Generator< std::optional< T > > optional(Generator< T > gen)
Generate optional values (present or absent).
Definition combinators.h:256