diff --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv --- a/libcxx/docs/Status/FormatPaper.csv +++ b/libcxx/docs/Status/FormatPaper.csv @@ -33,5 +33,5 @@ `[format.range] `_,"Formatting for ranges: sequences",,Mark de Wever,|In Progress|, `[format.range] `_,"Formatting for ranges: associative",,Mark de Wever,, `[format.range] `_,"Formatting for ranges: container adaptors",,Mark de Wever,, -`[format.range] `_,"Formatting for ranges: ``pair`` and ``tuple``",,Mark de Wever,|In Progress|, +`[format.range] `_,"Formatting for ranges: ``pair`` and ``tuple``",,Mark de Wever,|Complete|,Clang 16 `[format.range] `_,"Formatting for ranges: ``vector``",,Mark de Wever,, diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -309,6 +309,7 @@ __format/formatter_output.h __format/formatter_pointer.h __format/formatter_string.h + __format/formatter_tuple.h __format/parser_std_format_spec.h __format/range_default_formatter.h __format/unicode.h diff --git a/libcxx/include/__chrono/statically_widen.h b/libcxx/include/__chrono/statically_widen.h --- a/libcxx/include/__chrono/statically_widen.h +++ b/libcxx/include/__chrono/statically_widen.h @@ -26,20 +26,20 @@ # ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS template <__fmt_char_type _CharT> -consteval const _CharT* __statically_widen(const char* __str, const wchar_t* __wstr) { +_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* __statically_widen(const char* __str, const wchar_t* __wstr) { if constexpr (same_as<_CharT, char>) return __str; else return __wstr; } # define _LIBCPP_STATICALLY_WIDEN(_CharT, __str) ::std::__statically_widen<_CharT>(__str, L##__str) -# else // _LIBCPP_HAS_NO_WIDE_CHARACTERS +# else // _LIBCPP_HAS_NO_WIDE_CHARACTERS // Without this indirection the unit test test/libcxx/modules_include.sh.cpp // fails for the CI build "No wide characters". This seems like a bug. // TODO FMT investigate why this is needed. template <__fmt_char_type _CharT> -consteval const _CharT* __statically_widen(const char* __str) { +_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* __statically_widen(const char* __str) { return __str; } # define _LIBCPP_STATICALLY_WIDEN(_CharT, __str) ::std::__statically_widen<_CharT>(__str) diff --git a/libcxx/include/__format/formatter.h b/libcxx/include/__format/formatter.h --- a/libcxx/include/__format/formatter.h +++ b/libcxx/include/__format/formatter.h @@ -38,6 +38,15 @@ formatter& operator=(const formatter&) = delete; }; +# if _LIBCPP_STD_VER > 20 + +template +_LIBCPP_HIDE_FROM_ABI constexpr void __set_debug_format(_Tp& __formatter) { + if constexpr (requires { __formatter.set_debug_format(); }) + __formatter.set_debug_format(); +} + +# endif //_LIBCPP_STD_VER > 20 #endif //_LIBCPP_STD_VER > 17 _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/__format/formatter_tuple.h b/libcxx/include/__format/formatter_tuple.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/formatter_tuple.h @@ -0,0 +1,178 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___FORMAT_FORMATTER_TUPLE_H +#define _LIBCPP___FORMAT_FORMATTER_TUPLE_H + +#include <__algorithm/ranges_copy.h> +#include <__availability> +#include <__chrono/statically_widen.h> +#include <__config> +#include <__format/concepts.h> +#include <__format/format_args.h> +#include <__format/format_context.h> +#include <__format/format_error.h> +#include <__format/format_parse_context.h> +#include <__format/formatter.h> +#include <__format/formatter_output.h> +#include <__format/parser_std_format_spec.h> +#include <__iterator/back_insert_iterator.h> +#include <__type_traits/remove_cvref.h> +#include <__utility/integer_sequence.h> +#include <__utility/pair.h> +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 20 + +template <__fmt_char_type _CharT, class _Tuple, formattable<_CharT>... _Args> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT __formatter_tuple { + _LIBCPP_HIDE_FROM_ABI constexpr void set_separator(basic_string_view<_CharT> __separator) { + __separator_ = __separator; + } + _LIBCPP_HIDE_FROM_ABI constexpr void + set_brackets(basic_string_view<_CharT> __opening_bracket, basic_string_view<_CharT> __closing_bracket) { + __opening_bracket_ = __opening_bracket; + __closing_bracket_ = __closing_bracket; + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __parse_ctx) { + const _CharT* __begin = __parser_.__parse(__parse_ctx, __format_spec::__fields_tuple); + + // [format.tuple]/7 + // ... For each element e in underlying_, if e.set_debug_format() + // is a valid expression, calls e.set_debug_format(). + // TODO FMT this can be removed when P2733 is accepted. + __for_each_index_sequence(make_index_sequence(), [&] { + std::__set_debug_format(std::get<_Index>(__underlying_)); + }); + + const _CharT* __end = __parse_ctx.end(); + if (__begin == __end) + return __begin; + + if (*__begin == _CharT('m')) { + if constexpr (sizeof...(_Args) == 2) { + set_separator(_LIBCPP_STATICALLY_WIDEN(_CharT, ": ")); + set_brackets({}, {}); + ++__begin; + } else + std::__throw_format_error("The format specifier m requires a pair or a two-element tuple"); + } else if (*__begin == _CharT('n')) { + set_brackets({}, {}); + ++__begin; + } + + if (__begin != __end && *__begin != _CharT('}')) + std::__throw_format_error("The format-spec should consume the input or end with a '}'"); + + return __begin; + } + + template + typename _FormatContext::iterator _LIBCPP_HIDE_FROM_ABI + format(conditional_t<(formattable && ...), const _Tuple&, _Tuple&> __tuple, + _FormatContext& __ctx) const { + __format_spec::__parsed_specifications<_CharT> __specs = __parser_.__get_parsed_std_specifications(__ctx); + + if (!__specs.__has_width()) + return __format_tuple(__tuple, __ctx); + + basic_string<_CharT> __str; + + // Since the output is written to a different iterator a new context is + // created. Since the underlying formatter uses the default formatting it + // doesn't need a locale or the formatting arguments. So creating a new + // context works. + // + // This solution works for this formatter, but it will not work for the + // range_formatter. In that patch a generic solution is work in progress. + // Once that is finished it can be used here. (The range_formatter will use + // these features so it's easier to add it there and then port it.) + // + // TODO FMT Use formatting wrapping used in the range_formatter. + basic_format_context __c = std::__format_context_create( + back_insert_iterator{__str}, + basic_format_args>, _CharT>>{}); + + __format_tuple(__tuple, __c); + + return __formatter::__write_string_no_precision(basic_string_view{__str}, __ctx.out(), __specs); + } + + template + _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator __format_tuple(auto&& __tuple, _FormatContext& __ctx) const { + __ctx.advance_to(std::ranges::copy(__opening_bracket_, __ctx.out()).out); + + __for_each_index_sequence(make_index_sequence(), [&] { + if constexpr (_Index) + __ctx.advance_to(std::ranges::copy(__separator_, __ctx.out()).out); + + // During review Victor suggested to make the exposition only + // __underlying_ member a local variable. Currently the Standard + // requires nested debug-enabled formatter specializations not to + // output escaped output. P2733 fixes that bug, once accepted the + // code below can be used. + // (Note when a paper allows parsing a tuple-underlying-spec the + // exposition only member needs to be a class member. Earlier + // revisions of P2286 proposed that, but this was not pursued, + // due to time constrains and complexity of the matter.) + // TODO FMT This can be updated after P2733 is accepted. +# if 0 + // P2286 uses an exposition only member in the formatter + // tuple, _CharT>...> __underlying_; + // This was used in earlier versions of the paper since + // __underlying_.parse(...) was called. This is no longer the case + // so we can reduce the scope of the formatter. + // + // It does require the underlying's parse effect to be moved here too. + using _Arg = tuple_element<_Index, decltype(__tuple)>; + formatter, _CharT> __underlying; + + // [format.tuple]/7 + // ... For each element e in underlying_, if e.set_debug_format() + // is a valid expression, calls e.set_debug_format(). + std::__set_debug_format(__underlying); +# else + __ctx.advance_to(std::get<_Index>(__underlying_).format(std::get<_Index>(__tuple), __ctx)); +# endif + }); + + return std::ranges::copy(__closing_bracket_, __ctx.out()).out; + } + + __format_spec::__parser<_CharT> __parser_{.__alignment_ = __format_spec::__alignment::__left}; + +private: + tuple, _CharT>...> __underlying_; + basic_string_view<_CharT> __separator_ = _LIBCPP_STATICALLY_WIDEN(_CharT, ", "); + basic_string_view<_CharT> __opening_bracket_ = _LIBCPP_STATICALLY_WIDEN(_CharT, "("); + basic_string_view<_CharT> __closing_bracket_ = _LIBCPP_STATICALLY_WIDEN(_CharT, ")"); +}; + +template <__fmt_char_type _CharT, formattable<_CharT>... _Args> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter, _CharT> + : public __formatter_tuple<_CharT, pair<_Args...>, _Args...> {}; + +template <__fmt_char_type _CharT, formattable<_CharT>... _Args> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter, _CharT> + : public __formatter_tuple<_CharT, tuple<_Args...>, _Args...> {}; + +#endif //_LIBCPP_STD_VER > 20 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FORMAT_FORMATTER_TUPLE_H diff --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h --- a/libcxx/include/__format/parser_std_format_spec.h +++ b/libcxx/include/__format/parser_std_format_spec.h @@ -111,6 +111,13 @@ uint8_t __precision_ : 1 {false}; uint8_t __locale_specific_form_ : 1 {false}; uint8_t __type_ : 1 {false}; + // Determines the valid values for fill. + // + // Originally the fill could be any character except { and }. Range-based + // formatters use the colon to mark the beginning of the + // underlying-format-spec. To avoid parsing ambiguities these formatter + // specializations prohibit the use of the colon as a fill character. + uint8_t __allow_colon_in_fill_ : 1 {false}; }; // By not placing this constant in the formatter class it's not duplicated for @@ -131,6 +138,10 @@ inline constexpr __fields __fields_string{.__precision_ = true, .__type_ = true}; inline constexpr __fields __fields_pointer{.__type_ = true}; +# if _LIBCPP_STD_VER > 20 +inline constexpr __fields __fields_tuple{.__type_ = false, .__allow_colon_in_fill_ = true}; +# endif + enum class _LIBCPP_ENUM_VIS __alignment : uint8_t { /// No alignment is set in the format string. __default, @@ -259,7 +270,7 @@ if (__begin == __end) return __begin; - if (__parse_fill_align(__begin, __end) && __begin == __end) + if (__parse_fill_align(__begin, __end, __fields.__allow_colon_in_fill_) && __begin == __end) return __begin; if (__fields.__sign_ && __parse_sign(__begin) && __begin == __end) @@ -367,13 +378,17 @@ return false; } - _LIBCPP_HIDE_FROM_ABI constexpr bool __parse_fill_align(const _CharT*& __begin, const _CharT* __end) { + // range-fill and tuple-fill are identical + _LIBCPP_HIDE_FROM_ABI constexpr bool + __parse_fill_align(const _CharT*& __begin, const _CharT* __end, bool __use_range_fill) { _LIBCPP_ASSERT(__begin != __end, "when called with an empty input the function will cause " "undefined behavior by evaluating data not in the input"); if (__begin + 1 != __end) { if (__parse_alignment(*(__begin + 1))) { - if (*__begin == _CharT('{') || *__begin == _CharT('}')) - __throw_format_error("The format-spec fill field contains an invalid character"); + if (__use_range_fill && (*__begin == _CharT('{') || *__begin == _CharT('}') || *__begin == _CharT(':'))) + std::__throw_format_error("The format-spec range-fill field contains an invalid character"); + else if (*__begin == _CharT('{') || *__begin == _CharT('}')) + std::__throw_format_error("The format-spec fill field contains an invalid character"); __fill_ = *__begin; __begin += 2; diff --git a/libcxx/include/__utility/integer_sequence.h b/libcxx/include/__utility/integer_sequence.h --- a/libcxx/include/__utility/integer_sequence.h +++ b/libcxx/include/__utility/integer_sequence.h @@ -137,6 +137,13 @@ template using index_sequence_for = make_index_sequence; +# if _LIBCPP_STD_VER > 17 +// Executes __func for every element in an index_sequence. +inline constexpr auto __for_each_index_sequence = [](index_sequence<_Index...>, auto __func) { + (__func.template operator()<_Index>(), ...); +}; +# endif // _LIBCPP_STD_VER > 17 + #endif // _LIBCPP_STD_VER > 11 _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -191,6 +191,7 @@ #include <__format/formatter_integer.h> #include <__format/formatter_pointer.h> #include <__format/formatter_string.h> +#include <__format/formatter_tuple.h> #include <__format/parser_std_format_spec.h> #include <__format/range_default_formatter.h> #include <__format/unicode.h> diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -845,6 +845,7 @@ module formatter_output { private header "__format/formatter_output.h" } module formatter_pointer { private header "__format/formatter_pointer.h" } module formatter_string { private header "__format/formatter_string.h" } + module formatter_tuple { private header "__format/formatter_tuple.h" } module parser_std_format_spec { private header "__format/parser_std_format_spec.h" } module range_default_formatter { private header "__format/range_default_formatter.h" } module unicode { private header "__format/unicode.h" } diff --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp --- a/libcxx/test/libcxx/private_headers.verify.cpp +++ b/libcxx/test/libcxx/private_headers.verify.cpp @@ -341,6 +341,7 @@ #include <__format/formatter_output.h> // expected-error@*:* {{use of private header from outside its module: '__format/formatter_output.h'}} #include <__format/formatter_pointer.h> // expected-error@*:* {{use of private header from outside its module: '__format/formatter_pointer.h'}} #include <__format/formatter_string.h> // expected-error@*:* {{use of private header from outside its module: '__format/formatter_string.h'}} +#include <__format/formatter_tuple.h> // expected-error@*:* {{use of private header from outside its module: '__format/formatter_tuple.h'}} #include <__format/parser_std_format_spec.h> // expected-error@*:* {{use of private header from outside its module: '__format/parser_std_format_spec.h'}} #include <__format/range_default_formatter.h> // expected-error@*:* {{use of private header from outside its module: '__format/range_default_formatter.h'}} #include <__format/unicode.h> // expected-error@*:* {{use of private header from outside its module: '__format/unicode.h'}} diff --git a/libcxx/test/libcxx/utilities/intseq/for_each_index_sequence.pass.cpp b/libcxx/test/libcxx/utilities/intseq/for_each_index_sequence.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/intseq/for_each_index_sequence.pass.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// + +// inline constexpr auto __for_each_index_sequence = [](index_sequence<_Index...>, auto __func) + +#include +#include + +#include "test_macros.h" + +constexpr bool test() { + int count = 0; + std::__for_each_index_sequence(std::make_index_sequence<8>(), [&] { count += _Index; }); + assert(count == 28); + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp --- a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp @@ -224,8 +224,8 @@ assert_is_not_formattable, CharT>(); - assert_is_not_formattable, CharT>(); - assert_is_not_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); } class c { diff --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h --- a/libcxx/test/std/utilities/format/format.functions/format_tests.h +++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h @@ -207,7 +207,8 @@ check(SV("hello _world__"), SV("hello {:_^8}"), world); check(SV("hello world___"), SV("hello {:_<8}"), world); - check(SV("hello >>>world"), SV("hello {:>>8}"), world); + // The fill character ':' is allowed here (P0645) but not in ranges (P2286). + check(SV("hello :::world"), SV("hello {::>8}"), world); check(SV("hello <<8}"), world); check(SV("hello ^^^world"), SV("hello {:^>8}"), world); @@ -441,9 +442,10 @@ check(SV("answer is 'false '"), SV("answer is '{:<8s}'"), false); check(SV("answer is ' false '"), SV("answer is '{:^8s}'"), false); - check(SV("answer is '---true'"), SV("answer is '{:->7}'"), true); - check(SV("answer is 'true---'"), SV("answer is '{:-<7}'"), true); - check(SV("answer is '-true--'"), SV("answer is '{:-^7}'"), true); + // The fill character ':' is allowed here (P0645) but not in ranges (P2286). + check(SV("answer is ':::true'"), SV("answer is '{::>7}'"), true); + check(SV("answer is 'true:::'"), SV("answer is '{::<7}'"), true); + check(SV("answer is ':true::'"), SV("answer is '{::^7}'"), true); check(SV("answer is '---false'"), SV("answer is '{:->8s}'"), false); check(SV("answer is 'false---'"), SV("answer is '{:-<8s}'"), false); @@ -495,9 +497,10 @@ check(SV("answer is '1 '"), SV("answer is '{:<6d}'"), true); check(SV("answer is ' 1 '"), SV("answer is '{:^6d}'"), true); - check(SV("answer is '*****0'"), SV("answer is '{:*>6d}'"), false); - check(SV("answer is '0*****'"), SV("answer is '{:*<6d}'"), false); - check(SV("answer is '**0***'"), SV("answer is '{:*^6d}'"), false); + // The fill character ':' is allowed here (P0645) but not in ranges (P2286). + check(SV("answer is ':::::0'"), SV("answer is '{::>6d}'"), false); + check(SV("answer is '0:::::'"), SV("answer is '{::<6d}'"), false); + check(SV("answer is '::0:::'"), SV("answer is '{::^6d}'"), false); // Test whether zero padding is ignored check(SV("answer is ' 1'"), SV("answer is '{:>06d}'"), true); @@ -581,9 +584,10 @@ check(SV("answer is '42 '"), SV("answer is '{:<7}'"), I(42)); check(SV("answer is ' 42 '"), SV("answer is '{:^7}'"), I(42)); - check(SV("answer is '*****42'"), SV("answer is '{:*>7}'"), I(42)); - check(SV("answer is '42*****'"), SV("answer is '{:*<7}'"), I(42)); - check(SV("answer is '**42***'"), SV("answer is '{:*^7}'"), I(42)); + // The fill character ':' is allowed here (P0645) but not in ranges (P2286). + check(SV("answer is ':::::42'"), SV("answer is '{::>7}'"), I(42)); + check(SV("answer is '42:::::'"), SV("answer is '{::<7}'"), I(42)); + check(SV("answer is '::42:::'"), SV("answer is '{::^7}'"), I(42)); // Test whether zero padding is ignored check(SV("answer is ' 42'"), SV("answer is '{:>07}'"), I(42)); @@ -709,9 +713,10 @@ check(SV("answer is '* '"), SV("answer is '{:<6c}'"), I(42)); check(SV("answer is ' * '"), SV("answer is '{:^6c}'"), I(42)); - check(SV("answer is '-----*'"), SV("answer is '{:->6c}'"), I(42)); - check(SV("answer is '*-----'"), SV("answer is '{:-<6c}'"), I(42)); - check(SV("answer is '--*---'"), SV("answer is '{:-^6c}'"), I(42)); + // The fill character ':' is allowed here (P0645) but not in ranges (P2286). + check(SV("answer is ':::::*'"), SV("answer is '{::>6c}'"), I(42)); + check(SV("answer is '*:::::'"), SV("answer is '{::<6c}'"), I(42)); + check(SV("answer is '::*:::'"), SV("answer is '{::^6c}'"), I(42)); // *** Sign *** check(SV("answer is *"), SV("answer is {:c}"), I(42)); @@ -893,9 +898,10 @@ check(SV("answer is '* '"), SV("answer is '{:<6c}'"), CharT('*')); check(SV("answer is ' * '"), SV("answer is '{:^6c}'"), CharT('*')); - check(SV("answer is '-----*'"), SV("answer is '{:->6}'"), CharT('*')); - check(SV("answer is '*-----'"), SV("answer is '{:-<6}'"), CharT('*')); - check(SV("answer is '--*---'"), SV("answer is '{:-^6}'"), CharT('*')); + // The fill character ':' is allowed here (P0645) but not in ranges (P2286). + check(SV("answer is ':::::*'"), SV("answer is '{::>6}'"), CharT('*')); + check(SV("answer is '*:::::'"), SV("answer is '{::<6}'"), CharT('*')); + check(SV("answer is '::*:::'"), SV("answer is '{::^6}'"), CharT('*')); check(SV("answer is '-----*'"), SV("answer is '{:->6c}'"), CharT('*')); check(SV("answer is '*-----'"), SV("answer is '{:-<6c}'"), CharT('*')); @@ -955,9 +961,10 @@ check(SV("answer is '42 '"), SV("answer is '{:<7d}'"), CharT('*')); check(SV("answer is ' 42 '"), SV("answer is '{:^7d}'"), CharT('*')); - check(SV("answer is '*****42'"), SV("answer is '{:*>7d}'"), CharT('*')); - check(SV("answer is '42*****'"), SV("answer is '{:*<7d}'"), CharT('*')); - check(SV("answer is '**42***'"), SV("answer is '{:*^7d}'"), CharT('*')); + // The fill character ':' is allowed here (P0645) but not in ranges (P2286). + check(SV("answer is ':::::42'"), SV("answer is '{::>7d}'"), CharT('*')); + check(SV("answer is '42:::::'"), SV("answer is '{::<7d}'"), CharT('*')); + check(SV("answer is '::42:::'"), SV("answer is '{::^7d}'"), CharT('*')); // Test whether zero padding is ignored check(SV("answer is ' 42'"), SV("answer is '{:>07d}'"), CharT('*')); @@ -1029,9 +1036,10 @@ check(SV("answer is '1p-2 '"), SV("answer is '{:<7a}'"), F(0.25)); check(SV("answer is ' 1p-2 '"), SV("answer is '{:^7a}'"), F(0.25)); - check(SV("answer is '---1p-3'"), SV("answer is '{:->7a}'"), F(125e-3)); - check(SV("answer is '1p-3---'"), SV("answer is '{:-<7a}'"), F(125e-3)); - check(SV("answer is '-1p-3--'"), SV("answer is '{:-^7a}'"), F(125e-3)); + // The fill character ':' is allowed here (P0645) but not in ranges (P2286). + check(SV("answer is ':::1p-3'"), SV("answer is '{::>7a}'"), F(125e-3)); + check(SV("answer is '1p-3:::'"), SV("answer is '{::<7a}'"), F(125e-3)); + check(SV("answer is ':1p-3::'"), SV("answer is '{::^7a}'"), F(125e-3)); check(SV("answer is '***inf'"), SV("answer is '{:*>6a}'"), std::numeric_limits::infinity()); check(SV("answer is 'inf***'"), SV("answer is '{:*<6a}'"), std::numeric_limits::infinity()); @@ -2591,9 +2599,10 @@ check(SV("answer is '0x0 '"), SV("answer is '{:<6}'"), P(nullptr)); check(SV("answer is ' 0x0 '"), SV("answer is '{:^6}'"), P(nullptr)); - check(SV("answer is '---0x0'"), SV("answer is '{:->6}'"), P(nullptr)); - check(SV("answer is '0x0---'"), SV("answer is '{:-<6}'"), P(nullptr)); - check(SV("answer is '-0x0--'"), SV("answer is '{:-^6}'"), P(nullptr)); + // The fill character ':' is allowed here (P0645) but not in ranges (P2286). + check(SV("answer is ':::0x0'"), SV("answer is '{::>6}'"), P(nullptr)); + check(SV("answer is '0x0:::'"), SV("answer is '{::<6}'"), P(nullptr)); + check(SV("answer is ':0x0::'"), SV("answer is '{::^6}'"), P(nullptr)); // *** Sign *** check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), P(nullptr)); diff --git a/libcxx/test/std/utilities/format/format.tuple/format.functions.format.pass.cpp b/libcxx/test/std/utilities/format/format.tuple/format.functions.format.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.tuple/format.functions.format.pass.cpp @@ -0,0 +1,66 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// + +// template... Ts> +// struct formatter, charT> +// +// tested in the format functions +// +// template +// string format(format-string fmt, const Args&... args); +// template +// wstring format(wformat-string fmt, const Args&... args); + +#include +#include + +#include "format.functions.tests.h" +#include "test_format_string.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_LOCALIZATION +# include +# include +#endif + +auto test = []( + std::basic_string_view expected, test_format_string fmt, Args&&... args) { + std::basic_string out = std::format(fmt, std::forward(args)...); +#ifndef TEST_HAS_NO_LOCALIZATION + if constexpr (std::same_as) + if (out != expected) + std::cerr << "\nFormat string " << fmt.get() << "\nExpected output " << expected << "\nActual output " << out + << '\n'; +#endif // TEST_HAS_NO_LOCALIZATION + assert(out == expected); +}; + +auto test_exception = [](std::string_view, std::basic_string_view, Args&&...) { + // After P2216 most exceptions thrown by std::format become ill-formed. + // Therefore this tests does nothing. + // A basic ill-formed test is done in format.verify.cpp + // The exceptions are tested by other functions that don't use the basic-format-string as fmt argument. +}; + +int main(int, char**) { + run_tests(test, test_exception); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + run_tests(test, test_exception); +#endif + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.tuple/format.functions.format.verify.cpp b/libcxx/test/std/utilities/format/format.tuple/format.functions.format.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.tuple/format.functions.format.verify.cpp @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +#include + +#include +#include + +#include "test_macros.h" + +// clang-format off + +void f() { + std::format("{::}", std::make_tuple(0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} + + std::format("{::^}", std::make_tuple(0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} + + std::format("{:+}", std::make_pair(0, 0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} + + std::format("{:m}", std::make_tuple(0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} + + std::format("{:m}", std::make_tuple(0, 0, 0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + std::format(L"{::}", std::make_tuple(0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} + + std::format(L"{::^}", std::make_tuple(0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} + + std::format(L"{:+}", std::make_pair(0, 0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} + + std::format(L"{:m}", std::make_tuple(0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} + + std::format(L"{:m}", std::make_tuple(0, 0, 0)); // expected-error-re{{call to consteval function '{{.*}}' is not a constant expression}} + // expected-note@*:* {{non-constexpr function '__throw_format_error' cannot be used in a constant expression}} +#endif +} diff --git a/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h b/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h @@ -0,0 +1,390 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_STD_UTILITIES_FORMAT_FORMAT_TUPLE_FORMAT_TESTS_H +#define TEST_STD_UTILITIES_FORMAT_FORMAT_TUPLE_FORMAT_TESTS_H + +#include +#include + +#include "make_string.h" + +#define STR(S) MAKE_STRING(CharT, S) +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +struct context {}; + +template <> +struct context { + using type = std::format_context; +}; + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS +template <> +struct context { + using type = std::wformat_context; +}; +#endif + +template +using context_t = typename context::type; + +enum class color { black, red, gold }; + +template +struct std::formatter : std::formatter, CharT> { + static constexpr basic_string_view color_names[] = {SV("black"), SV("red"), SV("gold")}; + auto format(color c, auto& ctx) const { + return formatter, CharT>::format(color_names[static_cast(c)], ctx); + } +}; + +// +// Generic tests for a tuple and pair with two elements. +// +template +void test_tuple_or_pair_int_int(TestFunction check, ExceptionTest check_exception, TupleOrPair&& input) { + check(SV("(42, 99)"), SV("{}"), input); + + // *** align-fill & width *** + check(SV("(42, 99) "), SV("{:13}"), input); + check(SV("(42, 99)*****"), SV("{:*<13}"), input); + check(SV("__(42, 99)___"), SV("{:_^13}"), input); + check(SV("#####(42, 99)"), SV("{:#>13}"), input); + + check(SV("(42, 99) "), SV("{:{}}"), input, 13); + check(SV("(42, 99)*****"), SV("{:*<{}}"), input, 13); + check(SV("__(42, 99)___"), SV("{:_^{}}"), input, 13); + check(SV("#####(42, 99)"), SV("{:#>{}}"), input, 13); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** type *** + check(SV("__42: 99___"), SV("{:_^11m}"), input); + check(SV("__42, 99___"), SV("{:_^11n}"), input); + + for (CharT c : SV("aAbBcdeEfFgGopsxX?")) { + check_exception("The format-spec should consume the input or end with a '}'", + std::basic_string_view{STR("{:") + c + STR("}")}, + input); + } +} + +template +void test_tuple_or_pair_int_string(TestFunction check, ExceptionTest check_exception, TupleOrPair&& input) { + check(SV("(42, \"hello\")"), SV("{}"), input); + + // *** align-fill & width *** + check(SV("(42, \"hello\") "), SV("{:18}"), input); + check(SV("(42, \"hello\")*****"), SV("{:*<18}"), input); + check(SV("__(42, \"hello\")___"), SV("{:_^18}"), input); + check(SV("#####(42, \"hello\")"), SV("{:#>18}"), input); + + check(SV("(42, \"hello\") "), SV("{:{}}"), input, 18); + check(SV("(42, \"hello\")*****"), SV("{:*<{}}"), input, 18); + check(SV("__(42, \"hello\")___"), SV("{:_^{}}"), input, 18); + check(SV("#####(42, \"hello\")"), SV("{:#>{}}"), input, 18); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** type *** + check(SV("__42: \"hello\"___"), SV("{:_^16m}"), input); + check(SV("__42, \"hello\"___"), SV("{:_^16n}"), input); + + for (CharT c : SV("aAbBcdeEfFgGopsxX?")) { + check_exception("The format-spec should consume the input or end with a '}'", + std::basic_string_view{STR("{:") + c + STR("}")}, + input); + } +} + +template +void test_escaping(TestFunction check, TupleOrPair&& input) { + static_assert(std::same_as(input))>, CharT>); + static_assert(std::same_as(input))>, std::basic_string>); + + check(SV(R"(('*', ""))"), SV("{}"), input); + + // Char + std::get<0>(input) = CharT('\t'); + check(SV(R"(('\t', ""))"), SV("{}"), input); + std::get<0>(input) = CharT('\n'); + check(SV(R"(('\n', ""))"), SV("{}"), input); + std::get<0>(input) = CharT('\0'); + check(SV(R"(('\u{0}', ""))"), SV("{}"), input); + + // String + std::get<0>(input) = CharT('*'); + std::get<1>(input) = SV("hellö"); + check(SV("('*', \"hellö\")"), SV("{}"), input); +} + +// +// pair tests +// + +template +void test_pair_int_int(TestFunction check, ExceptionTest check_exception) { + test_tuple_or_pair_int_int(check, check_exception, std::make_pair(42, 99)); +} + +template +void test_pair_int_string(TestFunction check, ExceptionTest check_exception) { + test_tuple_or_pair_int_string(check, check_exception, std::make_pair(42, SV("hello"))); +} + +// +// tuple tests +// + +template +void test_tuple_int(TestFunction check, ExceptionTest check_exception) { + auto input = std::make_tuple(42); + + check(SV("(42)"), SV("{}"), input); + + // *** align-fill & width *** + check(SV("(42) "), SV("{:9}"), input); + check(SV("(42)*****"), SV("{:*<9}"), input); + check(SV("__(42)___"), SV("{:_^9}"), input); + check(SV("#####(42)"), SV("{:#>9}"), input); + + check(SV("(42) "), SV("{:{}}"), input, 9); + check(SV("(42)*****"), SV("{:*<{}}"), input, 9); + check(SV("__(42)___"), SV("{:_^{}}"), input, 9); + check(SV("#####(42)"), SV("{:#>{}}"), input, 9); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** type *** + check_exception("The format specifier m requires a pair or a two-element tuple", SV("{:m}"), input); + check(SV("__42___"), SV("{:_^7n}"), input); + + for (CharT c : SV("aAbBcdeEfFgGopsxX?")) { + check_exception("The format-spec should consume the input or end with a '}'", + std::basic_string_view{STR("{:") + c + STR("}")}, + input); + } +} + +template +void test_tuple_int_string_color(TestFunction check, ExceptionTest check_exception) { + const auto input = std::make_tuple(42, SV("hello"), color::red); + + check(SV("(42, \"hello\", \"red\")"), SV("{}"), input); + + // *** align-fill & width *** + check(SV("(42, \"hello\", \"red\") "), SV("{:25}"), input); + check(SV("(42, \"hello\", \"red\")*****"), SV("{:*<25}"), input); + check(SV("__(42, \"hello\", \"red\")___"), SV("{:_^25}"), input); + check(SV("#####(42, \"hello\", \"red\")"), SV("{:#>25}"), input); + + check(SV("(42, \"hello\", \"red\") "), SV("{:{}}"), input, 25); + check(SV("(42, \"hello\", \"red\")*****"), SV("{:*<{}}"), input, 25); + check(SV("__(42, \"hello\", \"red\")___"), SV("{:_^{}}"), input, 25); + check(SV("#####(42, \"hello\", \"red\")"), SV("{:#>{}}"), input, 25); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** type *** + check_exception("The format specifier m requires a pair or a two-element tuple", SV("{:m}"), input); + check(SV("__42, \"hello\", \"red\"___"), SV("{:_^23n}"), input); + + for (CharT c : SV("aAbBcdeEfFgGopsxX?")) { + check_exception("The format-spec should consume the input or end with a '}'", + std::basic_string_view{STR("{:") + c + STR("}")}, + input); + } +} + +template +void test_tuple_int_int(TestFunction check, ExceptionTest check_exception) { + test_tuple_or_pair_int_int(check, check_exception, std::make_tuple(42, 99)); +} + +template +void test_tuple_int_string(TestFunction check, ExceptionTest check_exception) { + test_tuple_or_pair_int_string(check, check_exception, std::make_tuple(42, SV("hello"))); +} + +// +// nested tests +// + +template +void test_nested(TestFunction check, ExceptionTest check_exception, Nested&& input) { + // [format.formatter.spec]/2 + // A debug-enabled specialization of formatter additionally provides a + // public, constexpr, non-static member function set_­debug_­format() + // which modifies the state of the formatter to be as if the type of the + // std-format-spec parsed by the last call to parse were ?. + // pair and tuple are not debug-enabled specializations to the + // set_debug_format is not propagated. The paper + // P2733 Fix handling of empty specifiers in std::format + // addressed this. + + check(SV("(42, (hello, red))"), SV("{}"), input); + + // *** align-fill & width *** + check(SV("(42, (hello, red)) "), SV("{:23}"), input); + check(SV("(42, (hello, red))*****"), SV("{:*<23}"), input); + check(SV("__(42, (hello, red))___"), SV("{:_^23}"), input); + check(SV("#####(42, (hello, red))"), SV("{:#>23}"), input); + + check(SV("(42, (hello, red)) "), SV("{:{}}"), input, 23); + check(SV("(42, (hello, red))*****"), SV("{:*<{}}"), input, 23); + check(SV("__(42, (hello, red))___"), SV("{:_^{}}"), input, 23); + check(SV("#####(42, (hello, red))"), SV("{:#>{}}"), input, 23); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** type *** + check(SV("__42: (hello, red)___"), SV("{:_^21m}"), input); + check(SV("__42, (hello, red)___"), SV("{:_^21n}"), input); + + for (CharT c : SV("aAbBcdeEfFgGopsxX?")) { + check_exception("The format-spec should consume the input or end with a '}'", + std::basic_string_view{STR("{:") + c + STR("}")}, + input); + } +} + +template +void run_tests(TestFunction check, ExceptionTest check_exception) { + test_pair_int_int(check, check_exception); + test_pair_int_string(check, check_exception); + + test_tuple_int(check, check_exception); + test_tuple_int_int(check, check_exception); + test_tuple_int_string(check, check_exception); + test_tuple_int_string_color(check, check_exception); + + test_nested(check, check_exception, std::make_pair(42, std::make_pair(SV("hello"), color::red))); + test_nested(check, check_exception, std::make_pair(42, std::make_tuple(SV("hello"), color::red))); + test_nested(check, check_exception, std::make_tuple(42, std::make_pair(SV("hello"), color::red))); + test_nested(check, check_exception, std::make_tuple(42, std::make_tuple(SV("hello"), color::red))); + + test_escaping(check, std::make_pair(CharT('*'), STR(""))); + test_escaping(check, std::make_tuple(CharT('*'), STR(""))); + + // Test cvref-qualified types. + // clang-format off + check(SV("(42)"), SV("{}"), std::tuple< int >{42}); + check(SV("(42)"), SV("{}"), std::tuple{42}); + check(SV("(42)"), SV("{}"), std::tuple< volatile int >{42}); + check(SV("(42)"), SV("{}"), std::tuple{42}); + + int answer = 42; + check(SV("(42)"), SV("{}"), std::tuple< int& >{answer}); + check(SV("(42)"), SV("{}"), std::tuple{answer}); + check(SV("(42)"), SV("{}"), std::tuple< volatile int& >{answer}); + check(SV("(42)"), SV("{}"), std::tuple{answer}); + + check(SV("(42)"), SV("{}"), std::tuple< int&&>{42}); + check(SV("(42)"), SV("{}"), std::tuple{42}); + check(SV("(42)"), SV("{}"), std::tuple< volatile int&&>{42}); + check(SV("(42)"), SV("{}"), std::tuple{42}); + // clang-format on +} + +#endif // TEST_STD_UTILITIES_FORMAT_FORMAT_TUPLE_FORMAT_TESTS_H diff --git a/libcxx/test/std/utilities/format/format.tuple/format.functions.vformat.pass.cpp b/libcxx/test/std/utilities/format/format.tuple/format.functions.vformat.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.tuple/format.functions.vformat.pass.cpp @@ -0,0 +1,86 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// + +// template... Ts> +// struct formatter, charT> +// +// tested in the format functions +// +// string vformat(string_view fmt, format_args args); +// wstring vformat(wstring_view fmt, wformat_args args); + +#include +#include + +#include "test_macros.h" +#include "format.functions.tests.h" + +#ifndef TEST_HAS_NO_LOCALIZATION +# include +# include +#endif + +auto test = []( + std::basic_string_view expected, std::basic_string_view fmt, Args&&... args) { + std::basic_string out = std::vformat(fmt, std::make_format_args>(args...)); +#ifndef TEST_HAS_NO_LOCALIZATION + if constexpr (std::same_as) + if (out != expected) + std::cerr << "\nFormat string " << fmt << "\nExpected output " << expected << "\nActual output " << out + << '\n'; +#endif // TEST_HAS_NO_LOCALIZATION + assert(out == expected); +}; + +auto test_exception = + []( + [[maybe_unused]] std::string_view what, + [[maybe_unused]] std::basic_string_view fmt, + [[maybe_unused]] Args&&... args) { +#ifndef TEST_HAS_NO_EXCEPTIONS + try { + TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args>(args...)); +# if !defined(TEST_HAS_NO_LOCALIZATION) + if constexpr (std::same_as) + std::cerr << "\nFormat string " << fmt << "\nDidn't throw an exception.\n"; +# endif // !defined(TEST_HAS_NO_LOCALIZATION + assert(false); + } catch ([[maybe_unused]] const std::format_error& e) { +# if defined(_LIBCPP_VERSION) +# if !defined(TEST_HAS_NO_LOCALIZATION) + if constexpr (std::same_as) { + if (e.what() != what) + std::cerr << "\nFormat string " << fmt << "\nExpected exception " << what << "\nActual exception " + << e.what() << '\n'; + } +# endif // !defined(TEST_HAS_NO_LOCALIZATION + assert(e.what() == what); +# endif // defined(_LIBCPP_VERSION) + return; + } + assert(false); +#endif + }; + +int main(int, char**) { + run_tests(test, test_exception); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + run_tests(test, test_exception); +#endif + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.tuple/format.pass.cpp b/libcxx/test/std/utilities/format/format.tuple/format.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.tuple/format.pass.cpp @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// + +// template... Ts> +// struct formatter, charT> + +// template +// typename FormatContext::iterator +// format(see below& elems, FormatContext& ctx) const; + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions tests. + +#include +#include +#include +#include +#include + +#include "test_format_context.h" +#include "test_macros.h" +#include "make_string.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +void test(StringViewT expected, Arg arg) { + using CharT = typename StringViewT::value_type; + using String = std::basic_string; + using OutIt = std::back_insert_iterator; + using FormatCtxT = std::basic_format_context; + + const std::formatter formatter; + + String result; + OutIt out = std::back_inserter(result); + FormatCtxT format_ctx = test_format_context_create(out, std::make_format_args(arg)); + formatter.format(arg, format_ctx); + assert(result == expected); +} + +template +void test() { + test(SV("(1)"), std::tuple{1}); + test(SV("(1, 1)"), std::tuple{1, CharT('1')}); + test(SV("(1, 1)"), std::pair{1, CharT('1')}); + test(SV("(1, 1, 1)"), std::tuple{1, CharT('1'), 1.0}); +} + +void test() { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif +} + +int main(int, char**) { + test(); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.tuple/parse.pass.cpp b/libcxx/test/std/utilities/format/format.tuple/parse.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.tuple/parse.pass.cpp @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// + +// template... Ts> +// struct formatter, charT> + +// template +// constexpr typename ParseContext::iterator +// parse(ParseContext& ctx); + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions tests. + +#include +#include +#include +#include +#include + +#include "test_format_context.h" +#include "test_macros.h" +#include "make_string.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +constexpr void test(StringViewT fmt) { + using CharT = typename StringViewT::value_type; + auto parse_ctx = std::basic_format_parse_context(fmt); + std::formatter formatter; + static_assert(std::semiregular); + + std::same_as auto it = formatter.parse(parse_ctx); + assert(it == fmt.end() - (!fmt.empty() && fmt.back() == '}')); +} + +template +constexpr void test() { + test(SV("")); + test(SV("42")); + + test(SV("}")); + test(SV("42}")); +} + +template +constexpr void test() { + test>(); + test>(); + test>(); + test>(); +} + +constexpr bool test() { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.tuple/set_brackets.pass.cpp b/libcxx/test/std/utilities/format/format.tuple/set_brackets.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.tuple/set_brackets.pass.cpp @@ -0,0 +1,64 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// + +// template... Ts> +// struct formatter, charT> + +// constexpr void constexpr void set_brackets(basic_string_view opening, +// basic_string_view closing); + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions tests. + +#include +#include +#include + +#include "make_string.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +constexpr void test() { + std::formatter formatter; + formatter.set_brackets(SV("open"), SV("close")); + + // Note there is no direct way to validate this function modified the object. +} + +template +constexpr void test() { + test>(); + test>(); + test>(); + test>(); +} + +constexpr bool test() { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.tuple/set_separator.pass.cpp b/libcxx/test/std/utilities/format/format.tuple/set_separator.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.tuple/set_separator.pass.cpp @@ -0,0 +1,64 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// + +// class range_formatter +// template... Ts> +// struct formatter, charT> + +// constexpr void set_separator(basic_string_view sep); + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions tests. + +#include +#include +#include + +#include "make_string.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +constexpr void test() { + std::formatter formatter; + formatter.set_separator(SV("sep")); + + // Note there is no direct way to validate this function modified the object. +} + +template +constexpr void test() { + test>(); + test>(); + test>(); + test>(); +} + +constexpr bool test() { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot --- a/libcxx/utils/ci/run-buildbot +++ b/libcxx/utils/ci/run-buildbot @@ -194,6 +194,7 @@ --exclude '*.dat' \ --exclude 'escaped_output.*.pass.cpp' \ --exclude 'format_tests.h' \ + --exclude 'format.functions.tests.h' \ --exclude 'formatter.*.pass.cpp' \ --exclude 'grep.pass.cpp' \ --exclude 'locale-specific_form.pass.cpp' \