diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -196,7 +196,7 @@ "`P2231R1 `__","LWG","Missing constexpr in std::optional and std::variant","June 2021","|In progress|","13.0" "`P2325R3 `__","LWG","Views should not be required to be default constructible","June 2021","|In progress|","" "`P2210R2 `__","LWG",Superior String Splitting,"June 2021","","" -"`P2216R3 `__","LWG",std::format improvements,"June 2021","|Partial|","" +"`P2216R3 `__","LWG",std::format improvements,"June 2021","|Complete|","15.0" "`P2281R1 `__","LWG",Clarifying range adaptor objects,"June 2021","|Complete|","14.0" "`P2328R1 `__","LWG",join_view should join all views of ranges,"June 2021","","" "`P2367R0 `__","LWG",Remove misuses of list-initialization from Clause 24,"June 2021","","" 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 @@ -258,7 +258,7 @@ /** Determines whether the value stored is a width or an arg-id. */ uint32_t __width_as_arg : 1 {0}; -protected: + //protected: /** * Does the supplied std-format-spec contain a width field? * @@ -333,7 +333,7 @@ */ uint32_t __precision_as_arg : 1 {1}; -protected: + //protected: /** * Does the supplied std-format-spec contain a precision field? * diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -23,15 +23,26 @@ using format_args = basic_format_args; using wformat_args = basic_format_args; + // [format.fmt.string], class template basic-format-string + template + struct basic-format-string; // exposition only + + template + using format-string = // exposition only + basic-format-string...>; + template + using wformat-string = // exposition only + basic-format-string...>; + // [format.functions], formatting functions template - string format(string_view fmt, const Args&... args); + string format(format-string fmt, const Args&... args); template - wstring format(wstring_view fmt, const Args&... args); + wstring format(wformat-string fmt, const Args&... args); template - string format(const locale& loc, string_view fmt, const Args&... args); + string format(const locale& loc, format-string fmt, const Args&... args); template - wstring format(const locale& loc, wstring_view fmt, const Args&... args); + wstring format(const locale& loc, wformat-string fmt, const Args&... args); string vformat(string_view fmt, format_args args); wstring vformat(wstring_view fmt, wformat_args args); @@ -39,13 +50,13 @@ wstring vformat(const locale& loc, wstring_view fmt, wformat_args args); template - Out format_to(Out out, string_view fmt, const Args&... args); + Out format_to(Out out, format-string fmt, const Args&... args); template - Out format_to(Out out, wstring_view fmt, const Args&... args); + Out format_to(Out out, wformat-string fmt, const Args&... args); template - Out format_to(Out out, const locale& loc, string_view fmt, const Args&... args); + Out format_to(Out out, const locale& loc, format-string fmt, const Args&... args); template - Out format_to(Out out, const locale& loc, wstring_view fmt, const Args&... args); + Out format_to(Out out, const locale& loc, wformat-string fmt, const Args&... args); template Out vformat_to(Out out, string_view fmt, format_args args); @@ -64,27 +75,27 @@ }; template format_to_n_result format_to_n(Out out, iter_difference_t n, - string_view fmt, const Args&... args); + format-string fmt, const Args&... args); template format_to_n_result format_to_n(Out out, iter_difference_t n, - wstring_view fmt, const Args&... args); + wformat-string fmt, const Args&... args); template format_to_n_result format_to_n(Out out, iter_difference_t n, - const locale& loc, string_view fmt, + const locale& loc, format-string fmt, const Args&... args); template format_to_n_result format_to_n(Out out, iter_difference_t n, - const locale& loc, wstring_view fmt, + const locale& loc, wformat-string fmt, const Args&... args); template - size_t formatted_size(string_view fmt, const Args&... args); + size_t formatted_size(format-string fmt, const Args&... args); template - size_t formatted_size(wstring_view fmt, const Args&... args); + size_t formatted_size(wformat-string fmt, const Args&... args); template - size_t formatted_size(const locale& loc, string_view fmt, const Args&... args); + size_t formatted_size(const locale& loc, format-string fmt, const Args&... args); template - size_t formatted_size(const locale& loc, wstring_view fmt, const Args&... args); + size_t formatted_size(const locale& loc, wformat-string fmt, const Args&... args); // [format.formatter], formatter template struct formatter; @@ -203,8 +214,180 @@ namespace __format { +template +class _LIBCPP_TEMPLATE_VIS __compile_time_handle { +public: + _LIBCPP_HIDE_FROM_ABI + constexpr void __parse(basic_format_parse_context<_CharT>& __parse_ctx) const { __parse_(__parse_ctx); } + + template + _LIBCPP_HIDE_FROM_ABI constexpr void __enable() { + __parse_ = [](basic_format_parse_context<_CharT>& __parse_ctx) { + formatter<_Tp, _CharT> __f; + __parse_ctx.advance_to(__f.parse(__parse_ctx)); + }; + } + + _LIBCPP_HIDE_FROM_ABI constexpr __compile_time_handle() + : __parse_([](basic_format_parse_context<_CharT>&) { __throw_format_error("Not a handle"); }) {} + +private: + void (*__parse_)(basic_format_parse_context<_CharT>&); +}; + +// Dummy format_context only providing the parts used during constant +// validation of the basic-format-string. +template +struct _LIBCPP_TEMPLATE_VIS __compile_time_basic_format_context { +public: + using char_type = _CharT; + + _LIBCPP_HIDE_FROM_ABI constexpr explicit __compile_time_basic_format_context( + const __arg_meta_data* __args, const __compile_time_handle<_CharT>* __handles) + : __args_(__args), __handles_(__handles) {} + + // During the compile-time validation nothing needs to be written. + // Therefore all operations of this iterator are a NOP. + struct iterator { + _LIBCPP_HIDE_FROM_ABI constexpr iterator& operator=(_CharT) { return *this; } + _LIBCPP_HIDE_FROM_ABI constexpr iterator& operator*() { return *this; } + _LIBCPP_HIDE_FROM_ABI constexpr iterator operator++(int) { return *this; } + }; + + _LIBCPP_HIDE_FROM_ABI constexpr __arg_t arg(size_t __id) const { + // accessing array out of bounds is UB thus an error. + return __args_[__id].__type; + } + + _LIBCPP_HIDE_FROM_ABI constexpr const __compile_time_handle<_CharT>& __handle(size_t __id) const { + // accessing array out of bounds is UB thus an error. + return __handles_[__id]; + } + + _LIBCPP_HIDE_FROM_ABI constexpr iterator out() { return {}; } + _LIBCPP_HIDE_FROM_ABI constexpr void advance_to(iterator) {} + +private: + const __arg_meta_data* __args_; + const __compile_time_handle<_CharT>* __handles_; +}; + +template +consteval void __get_handle(__compile_time_handle<_CharT>* __handle) { + using _Context = __format::__compile_time_basic_format_context<_CharT>; + auto __s = __format::__make_storage_type<_Context, _Tp>(); + if (__s.__arg == __format::__arg_t::__handle) + __handle->template __enable<_Tp>(); + + if constexpr (sizeof...(_Args)) + __get_handle<_CharT, _Args...>(++__handle); +} + +template +consteval array<__compile_time_handle<_CharT>, sizeof...(_Args)> __get_handle() { + if constexpr (sizeof...(_Args) == 0) + return array<__compile_time_handle<_CharT>,0>{}; + else { + array<__compile_time_handle<_CharT>, sizeof...(_Args)> __result; + __get_handle<_CharT, _Args...>(__result.data()); + return __result; + } +} + +_LIBCPP_HIDE_FROM_ABI +constexpr void __compile_time_validate_integral(__arg_t __type) { + switch (__type) { + case __arg_t::__int: + case __arg_t::__long_long: +# ifndef _LIBCPP_HAS_NO_INT128 + case __arg_t::__i128: +# endif + case __arg_t::__unsigned: + case __arg_t::__unsigned_long_long: +# ifndef _LIBCPP_HAS_NO_INT128 + case __arg_t::__u128: +# endif + return; + + default: + __throw_format_error("Argument isn't an integral"); + } +} + +// _HasPrecision does the formatter have a precision? +template +_LIBCPP_HIDE_FROM_ABI constexpr void +__compile_time_validate_argument(basic_format_parse_context<_CharT>& __parse_ctx, + __compile_time_basic_format_context<_CharT>& __ctx) { + formatter<_Tp, _CharT> __formatter; + __parse_ctx.advance_to(__formatter.parse(__parse_ctx)); + // [format.string.std]/7 + // ... If the corresponding formatting argument is not of integral type, or + // its value is negative for precision or non-positive for width, an + // exception of type format_error is thrown. + // + // Validate whether the arguments are integrals. + if (__formatter.__width_needs_substitution()) + __compile_time_validate_integral(__ctx.arg(__formatter.__width)); + + if constexpr (_HasPrecision) + if (__formatter.__precision_needs_substitution()) + __compile_time_validate_integral(__ctx.arg(__formatter.__precision)); +} + +template +_LIBCPP_HIDE_FROM_ABI constexpr void __compile_time_visit_format_arg(basic_format_parse_context<_CharT>& __parse_ctx, + __compile_time_basic_format_context<_CharT>& __ctx, + __arg_t __type) { + switch (__type) { + case __arg_t::__none: + __throw_format_error("Invalid argument"); + case __arg_t::__boolean: + return __compile_time_validate_argument<_CharT, bool>(__parse_ctx, __ctx); + case __arg_t::__char_type: + return __compile_time_validate_argument<_CharT, _CharT>(__parse_ctx, __ctx); + case __arg_t::__int: + return __compile_time_validate_argument<_CharT, int>(__parse_ctx, __ctx); + case __arg_t::__long_long: + return __compile_time_validate_argument<_CharT, long long>(__parse_ctx, __ctx); + case __arg_t::__i128: +# ifndef _LIBCPP_HAS_NO_INT128 + return __compile_time_validate_argument<_CharT, __int128_t>(__parse_ctx, __ctx); +# else + __throw_format_error("Invalid argument"); +# endif + return; + case __arg_t::__unsigned: + return __compile_time_validate_argument<_CharT, unsigned>(__parse_ctx, __ctx); + case __arg_t::__unsigned_long_long: + return __compile_time_validate_argument<_CharT, unsigned long long>(__parse_ctx, __ctx); + case __arg_t::__u128: +# ifndef _LIBCPP_HAS_NO_INT128 + return __compile_time_validate_argument<_CharT, __uint128_t>(__parse_ctx, __ctx); +# else + __throw_format_error("Invalid argument"); +# endif + return; + case __arg_t::__float: + return __compile_time_validate_argument<_CharT, float, true>(__parse_ctx, __ctx); + case __arg_t::__double: + return __compile_time_validate_argument<_CharT, double, true>(__parse_ctx, __ctx); + case __arg_t::__long_double: + return __compile_time_validate_argument<_CharT, long double, true>(__parse_ctx, __ctx); + case __arg_t::__const_char_type_ptr: + return __compile_time_validate_argument<_CharT, const _CharT*, true>(__parse_ctx, __ctx); + case __arg_t::__string_view: + return __compile_time_validate_argument<_CharT, basic_string_view<_CharT>, true>(__parse_ctx, __ctx); + case __arg_t::__ptr: + return __compile_time_validate_argument<_CharT, const void*>(__parse_ctx, __ctx); + case __arg_t::__handle: + __throw_format_error("Handle shoule use __compile_time_validate_handle_argument"); + } + __throw_format_error("Invalid argument"); +} + template -_LIBCPP_HIDE_FROM_ABI const _CharT* +_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* __handle_replacement_field(const _CharT* __begin, const _CharT* __end, _ParseCtx& __parse_ctx, _Ctx& __ctx) { __format::__parse_number_result __r = @@ -224,19 +407,26 @@ "The replacement field arg-id should terminate at a ':' or '}'"); } - _VSTD::visit_format_arg( - [&](auto __arg) { - if constexpr (same_as) - __throw_format_error("Argument index out of bounds"); - else if constexpr (same_as::handle>) - __arg.format(__parse_ctx, __ctx); - else { - formatter __formatter; - __parse_ctx.advance_to(__formatter.parse(__parse_ctx)); - __ctx.advance_to(__formatter.format(__arg, __ctx)); - } - }, - __ctx.arg(__r.__value)); + if constexpr (same_as<_Ctx, __compile_time_basic_format_context<_CharT>>) { + __arg_t __type = __ctx.arg(__r.__value); + if (__type == __arg_t::__handle) + __ctx.__handle(__r.__value).__parse(__parse_ctx); + else + __compile_time_visit_format_arg(__parse_ctx, __ctx, __type); + } else + _VSTD::visit_format_arg( + [&](auto __arg) { + if constexpr (same_as) + __throw_format_error("Argument index out of bounds"); + else if constexpr (same_as::handle>) + __arg.format(__parse_ctx, __ctx); + else { + formatter __formatter; + __parse_ctx.advance_to(__formatter.parse(__parse_ctx)); + __ctx.advance_to(__formatter.format(__arg, __ctx)); + } + }, + __ctx.arg(__r.__value)); __begin = __parse_ctx.begin(); if (__begin == __end || *__begin != _CharT('}')) @@ -246,7 +436,7 @@ } template -_LIBCPP_HIDE_FROM_ABI typename _Ctx::iterator +_LIBCPP_HIDE_FROM_ABI constexpr typename _Ctx::iterator __vformat_to(_ParseCtx&& __parse_ctx, _Ctx&& __ctx) { using _CharT = typename _ParseCtx::char_type; static_assert(same_as); @@ -291,6 +481,35 @@ } // namespace __format +template +struct _LIBCPP_TEMPLATE_VIS __basic_format_string { + basic_string_view<_CharT> __str_; + + template + requires convertible_to> + consteval __basic_format_string(const _Tp& __str) : __str_{__str} { + __format::__vformat_to(basic_format_parse_context<_CharT>{__str_, sizeof...(_Args)}, + _Context{__meta_data_.data(), __handles_.data()}); + } + +private: + using _Context = __format::__compile_time_basic_format_context<_CharT>; + + static constexpr array<__format::__arg_meta_data, sizeof...(_Args)> __meta_data_{ + __format::__get_meta_data<_Context, remove_cvref_t<_Args>...>()}; + + static constexpr array<__format::__compile_time_handle<_CharT>, sizeof...(_Args)> __handles_{ + __format::__get_handle<_CharT, remove_cvref_t<_Args>...>()}; +}; + +template +using __format_string = __basic_format_string...>; + +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +template +using __wformat_string = __basic_format_string...>; +#endif + template requires(output_iterator<_OutIt, const _CharT&>) _LIBCPP_HIDE_FROM_ABI _OutIt __vformat_to( @@ -325,8 +544,8 @@ template _OutIt, class... _Args> _LIBCPP_ALWAYS_INLINE _LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT _OutIt -format_to(_OutIt __out_it, string_view __fmt, const _Args&... __args) { - return _VSTD::vformat_to(_VSTD::move(__out_it), __fmt, +format_to(_OutIt __out_it, __format_string<_Args...> __fmt, const _Args&... __args) { + return _VSTD::vformat_to(_VSTD::move(__out_it), __fmt.__str_, _VSTD::make_format_args(__args...)); } @@ -357,8 +576,8 @@ template _LIBCPP_ALWAYS_INLINE _LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT string -format(string_view __fmt, const _Args&... __args) { - return _VSTD::vformat(__fmt, _VSTD::make_format_args(__args...)); +format(__format_string<_Args...> __fmt, const _Args&... __args) { + return _VSTD::vformat(__fmt.__str_, _VSTD::make_format_args(__args...)); } #ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS diff --git a/libcxx/test/std/utilities/format/format.functions/format.pass.cpp b/libcxx/test/std/utilities/format/format.functions/format.pass.cpp --- a/libcxx/test/std/utilities/format/format.functions/format.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/format.pass.cpp @@ -27,58 +27,51 @@ #include #include "test_macros.h" -#include "format_tests.h" #ifndef TEST_HAS_NO_LOCALIZATION # include #endif -auto test = [](std::basic_string_view expected, std::basic_string_view fmt, - const Args&... args) { - std::basic_string out = std::format(fmt, 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'; +#ifdef _LIBCPP_VERSION +# define test_basic_format_string std::__basic_format_string +#else +# error "Please add a define for your platform" #endif - assert(out == expected); -}; -auto test_exception = [](std::string_view what, std::basic_string_view fmt, - const Args&... args) { -#ifndef TEST_HAS_NO_EXCEPTIONS - try { - std::format(fmt, args...); -# ifndef TEST_HAS_NO_LOCALIZATION - if constexpr (std::same_as) - std::cerr << "\nFormat string " << fmt << "\nDidn't throw an exception.\n"; -# endif - assert(false); - } catch (const std::format_error& e) { - if constexpr (std::same_as) -# if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_LOCALIZATION) - if (e.what() != what) - std::cerr << "\nFormat string " << fmt << "\nExpected exception " << what << "\nActual exception " - << e.what() << '\n'; -# endif - LIBCPP_ASSERT(e.what() == what); - return; - } - assert(false); +#define check(e, fmt, ...) \ + { \ + /* Force the proper type for expected. */ \ + /* When using a check function its argument does the conversion, here do it explicitly. */ \ + std::basic_string_view expected = e; \ + std::basic_string out = std::format(fmt __VA_OPT__(, __VA_ARGS__)); \ + assert(out == expected); \ + } \ + /* */ + +#ifdef TEST_HAS_NO_EXCEPTIONS +# define check_exception(...) +#else +# define check_exception(what, fmt, ...) \ + { \ + try { \ + std::format(fmt, __VA_OPT__(, __VA_ARGS__)); \ + assert(false); \ + } catch (std::format_error & e) { \ + LIBCPP_ASSERT(e.what() == what); \ + return; \ + } \ + assert(false); \ + } \ + /* */ #endif - (void)what; - (void)fmt; - (void)sizeof...(args); -}; -int main(int, char**) { - format_tests(test, test_exception); +#define TEST_FORMAT_USE_COMPILE_TIME_CHECK 1 +#include "format_tests.h" +int main(int, char**) { + format_tests(); #ifndef TEST_HAS_NO_WIDE_CHARACTERS - format_tests_char_to_wchar_t(test); - format_tests(test, test_exception); + format_tests_char_to_wchar_t(); +// format_tests(); #endif - - return 0; } diff --git a/libcxx/test/std/utilities/format/format.functions/format.verify.cpp b/libcxx/test/std/utilities/format/format.functions/format.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.functions/format.verify.cpp @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// 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 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 + +// + +#include + +// TODO std::format extensive char and wchar_t other functions just a few. + +void XXX() { std::format("{}", "hello"); } + +#if 1 +void test() { + // Invalid indexing + std::format("{}"); // expected-error {{is not a constant expression}} + std::format("{0}"); // expected-error {{is not a constant expression}} + std::format("{1}", 1); // expected-error {{is not a constant expression}} + std::format("{0}{}", 1, 2); // expected-error {{is not a constant expression}} + std::format("{}{0}", 1, 2); // expected-error {{is not a constant expression}} + + // invalid std-format-spec integer + std::format("{:%d}", "hello"); // expected-error {{is not a constant expression}} + std::format("{:.1}", 42); // expected-error {{is not a constant expression}} + + std::format("{0:{1}}", 42, "hello"); // expected-error {{is not a constant expression}} + + // invalid std-format-spec string + std::format("{0:{1}}", 42, "hello"); // expected-error {{is not a constant expression}} + std::format("{0:.{1}}", 42, "hello"); // expected-error {{is not a constant expression}} + + // TODO Add more errors when can't use the current tests. +} +#endif 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 @@ -26,6 +26,30 @@ #define SV(S) MAKE_STRING_VIEW(CharT, S) #define CSTR(S) MAKE_CSTRING(CharT, S) +#ifdef TEST_FORMAT_USE_COMPILE_TIME_CHECK +# define CHECK_TEMPLATE_ARGS +# define CHECK_FUNCTION_ARGS ... // 0 needed but allows comma before macro +# define CALL_CHECK_FUNCTION(f) f() +# define check_ill_formed(what, fmt, ...) \ + static_assert([]() { \ + if constexpr (requires { \ + []() { \ + }.template operator()<(test_basic_format_string(fmt), \ + void(), 0)>(); \ + }) \ + return false; \ + else \ + return true; \ + }()); \ + /* */ +#else +# define CHECK_TEMPLATE_ARGS , class TestFunction, class ExceptionTest +# define CHECK_FUNCTION_ARGS TestFunction check, ExceptionTest check_exception +# define CALL_CHECK_FUNCTION(f) f(check, check_exception) +# define check_ill_formed check_exception +#endif + template struct context {}; @@ -172,9 +196,10 @@ return result; } +#if 0 +template +void format_test_string(T world, T universe, CHECK_FUNCTION_ARGS) { -template -void format_test_string(T world, T universe, TestFunction check, ExceptionTest check_exception) { // *** Valid input tests *** // Unsed argument is ignored. TODO FMT what does the Standard mandate? @@ -232,13 +257,13 @@ check_exception("A format-spec width field shouldn't have a leading zero", SV("hello {:0}"), world); // *** width *** -#ifdef _LIBCPP_VERSION +# ifdef _LIBCPP_VERSION // This limit isn't specified in the Standard. static_assert(std::__format::__number_max == 2'147'483'647, "Update the assert and the test."); check_exception("The numeric value of the format-spec is too large", SV("{:2147483648}"), world); check_exception("The numeric value of the format-spec is too large", SV("{:5000000000}"), world); check_exception("The numeric value of the format-spec is too large", SV("{:10000000000}"), world); -#endif +# endif check_exception("A format-spec width field replacement should have a positive value", SV("hello {:{}}"), world, 0); check_exception("A format-spec arg-id replacement shouldn't have a negative value", SV("hello {:{}}"), world, -1); @@ -253,13 +278,13 @@ check_exception("Invalid arg-id", SV("hello {0:{01}}"), world, 1); // *** precision *** -#ifdef _LIBCPP_VERSION +# ifdef _LIBCPP_VERSION // This limit isn't specified in the Standard. static_assert(std::__format::__number_max == 2'147'483'647, "Update the assert and the test."); check_exception("The numeric value of the format-spec is too large", SV("{:.2147483648}"), world); check_exception("The numeric value of the format-spec is too large", SV("{:.5000000000}"), world); check_exception("The numeric value of the format-spec is too large", SV("{:.10000000000}"), world); -#endif +# endif // Precision 0 allowed, but not useful for string arguments. check(SV("hello "), SV("hello {:.{}}"), world, 0); @@ -282,14 +307,16 @@ check_exception("The format-spec should consume the input or end with a '}'", SV("hello {:L}"), world); // *** type *** +# ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK for (const auto& fmt : invalid_types("s")) check_exception("The format-spec type has a type not supported for a string argument", fmt, world); +# endif } template void format_test_string_unicode(TestFunction check) { (void)check; -#ifndef TEST_HAS_NO_UNICODE +# ifndef TEST_HAS_NO_UNICODE // ß requires one column check(SV("aßc"), SV("{}"), STR("aßc")); @@ -321,7 +348,7 @@ check(SV("a\u1110c---"), SV("{:-<7}"), STR("a\u1110c")); check(SV("-a\u1110c--"), SV("{:-^7}"), STR("a\u1110c")); check(SV("---a\u1110c"), SV("{:->7}"), STR("a\u1110c")); -#endif // TEST_HAS_NO_UNICODE +# endif // TEST_HAS_NO_UNICODE } template @@ -393,8 +420,10 @@ // See locale-specific_form.pass.cpp // *** type *** +# ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK for (const auto& fmt : invalid_types("bBdosxX")) check_exception("The format-spec type has a type not supported for a bool argument", fmt, true); +# endif } template @@ -479,8 +508,10 @@ // See locale-specific_form.pass.cpp // *** type *** +# ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK for (const auto& fmt : invalid_types("bBcdosxX")) check_exception("The format-spec type has a type not supported for a bool argument", fmt, true); +# endif } template @@ -611,8 +642,10 @@ // See locale-specific_form.pass.cpp // *** type *** +# ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK for (const auto& fmt : invalid_types("bBcdoxX")) check_exception("The format-spec type has a type not supported for an integer argument", fmt, 42); +# endif } template @@ -649,9 +682,10 @@ check(SV("answer is '*'"), SV("answer is '{:Lc}'"), I(42)); // *** type *** +# ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK for (const auto& fmt : invalid_types("bBcdoxX")) check_exception("The format-spec type has a type not supported for an integer argument", fmt, I(42)); - +# endif // *** Validate range *** // TODO FMT Update test after adding 128-bit support. if constexpr (sizeof(I) <= sizeof(long long)) { @@ -682,9 +716,9 @@ format_test_integer(check, check_exception); format_test_integer(check, check_exception); format_test_integer(check, check_exception); -#ifndef TEST_HAS_NO_INT128 +# ifndef TEST_HAS_NO_INT128 format_test_integer<__int128_t, CharT>(check, check_exception); -#endif +# endif // *** check the minma and maxima *** check(SV("-0b10000000"), SV("{:#b}"), std::numeric_limits::min()); check(SV("-0200"), SV("{:#o}"), std::numeric_limits::min()); @@ -738,9 +772,9 @@ format_test_integer(check, check_exception); format_test_integer(check, check_exception); format_test_integer(check, check_exception); -#ifndef TEST_HAS_NO_INT128 +# ifndef TEST_HAS_NO_INT128 format_test_integer<__uint128_t, CharT>(check, check_exception); -#endif +# endif // *** test the maxima *** check(SV("0b11111111"), SV("{:#b}"), std::numeric_limits::max()); check(SV("0377"), SV("{:#o}"), std::numeric_limits::max()); @@ -765,10 +799,9 @@ // TODO FMT Add __uint128_t test after implementing full range. } - -template -void format_test_char(TestFunction check, ExceptionTest check_exception) { - +#endif +template +void format_test_char(CHECK_FUNCTION_ARGS) { // ***** Char type ***** // *** align-fill & width *** check(SV("answer is '* '"), SV("answer is '{:6}'"), CharT('*')); @@ -790,30 +823,30 @@ check(SV("answer is '--*---'"), SV("answer is '{:-^6c}'"), CharT('*')); // *** Sign *** - check_exception("A sign field isn't allowed in this format-spec", SV("{:-}"), CharT('*')); - check_exception("A sign field isn't allowed in this format-spec", SV("{:+}"), CharT('*')); - check_exception("A sign field isn't allowed in this format-spec", SV("{: }"), CharT('*')); + check_ill_formed("A sign field isn't allowed in this format-spec", SV("{:-}"), CharT('*')); + check_ill_formed("A sign field isn't allowed in this format-spec", SV("{:+}"), CharT('*')); + check_ill_formed("A sign field isn't allowed in this format-spec", SV("{: }"), CharT('*')); - check_exception("A sign field isn't allowed in this format-spec", SV("{:-c}"), CharT('*')); - check_exception("A sign field isn't allowed in this format-spec", SV("{:+c}"), CharT('*')); - check_exception("A sign field isn't allowed in this format-spec", SV("{: c}"), CharT('*')); + check_ill_formed("A sign field isn't allowed in this format-spec", SV("{:-c}"), CharT('*')); + check_ill_formed("A sign field isn't allowed in this format-spec", SV("{:+c}"), CharT('*')); + check_ill_formed("A sign field isn't allowed in this format-spec", SV("{: c}"), CharT('*')); // *** alternate form *** - check_exception("An alternate form field isn't allowed in this format-spec", SV("{:#}"), CharT('*')); - check_exception("An alternate form field isn't allowed in this format-spec", SV("{:#c}"), CharT('*')); + check_ill_formed("An alternate form field isn't allowed in this format-spec", SV("{:#}"), CharT('*')); + check_ill_formed("An alternate form field isn't allowed in this format-spec", SV("{:#c}"), CharT('*')); // *** zero-padding *** - check_exception("A zero-padding field isn't allowed in this format-spec", SV("{:0}"), CharT('*')); - check_exception("A zero-padding field isn't allowed in this format-spec", SV("{:0c}"), CharT('*')); + check_ill_formed("A zero-padding field isn't allowed in this format-spec", SV("{:0}"), CharT('*')); + check_ill_formed("A zero-padding field isn't allowed in this format-spec", SV("{:0c}"), CharT('*')); // *** precision *** - check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), CharT('*')); - check_exception("The format-spec should consume the input or end with a '}'", SV("{:.0}"), CharT('*')); - check_exception("The format-spec should consume the input or end with a '}'", SV("{:.42}"), CharT('*')); + check_ill_formed("The format-spec should consume the input or end with a '}'", SV("{:.}"), CharT('*')); + check_ill_formed("The format-spec should consume the input or end with a '}'", SV("{:.0}"), CharT('*')); + check_ill_formed("The format-spec should consume the input or end with a '}'", SV("{:.42}"), CharT('*')); - check_exception("The format-spec should consume the input or end with a '}'", SV("{:.c}"), CharT('*')); - check_exception("The format-spec should consume the input or end with a '}'", SV("{:.0c}"), CharT('*')); - check_exception("The format-spec should consume the input or end with a '}'", SV("{:.42c}"), CharT('*')); + check_ill_formed("The format-spec should consume the input or end with a '}'", SV("{:.c}"), CharT('*')); + check_ill_formed("The format-spec should consume the input or end with a '}'", SV("{:.0c}"), CharT('*')); + check_ill_formed("The format-spec should consume the input or end with a '}'", SV("{:.42c}"), CharT('*')); // *** locale-specific form *** // Note it has no effect but it's allowed. @@ -821,12 +854,15 @@ check(SV("answer is '*'"), SV("answer is '{:Lc}'"), '*'); // *** type *** - for (const auto& fmt : invalid_types("bBcdoxX")) +#ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK + for (const auto& fmt : invalid_types("bBcdoxX")) { check_exception("The format-spec type has a type not supported for a char argument", fmt, CharT('*')); + } +#endif } -template -void format_test_char_as_integer(TestFunction check, ExceptionTest check_exception) { +template +void format_test_char_as_integer(CHECK_FUNCTION_ARGS) { // *** align-fill & width *** check(SV("answer is '42'"), SV("answer is '{:<1d}'"), CharT('*')); @@ -879,18 +915,20 @@ check(SV("answer is +0X00000002A"), SV("answer is {:+#012X}"), CharT('*')); // *** precision *** - check_exception("The format-spec should consume the input or end with a '}'", SV("{:.d}"), CharT('*')); - check_exception("The format-spec should consume the input or end with a '}'", SV("{:.0d}"), CharT('*')); - check_exception("The format-spec should consume the input or end with a '}'", SV("{:.42d}"), CharT('*')); + check_ill_formed("The format-spec should consume the input or end with a '}'", SV("{:.d}"), CharT('*')); + check_ill_formed("The format-spec should consume the input or end with a '}'", SV("{:.0d}"), CharT('*')); + check_ill_formed("The format-spec should consume the input or end with a '}'", SV("{:.42d}"), CharT('*')); // *** locale-specific form *** // See locale-specific_form.pass.cpp // *** type *** - for (const auto& fmt : invalid_types("bBcdoxX")) - check_exception("The format-spec type has a type not supported for a char argument", fmt, '*'); +#ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK + for (auto fmt : invalid_types("bBcdoxX")) + check_ill_formed("The format-spec type has a type not supported for a char argument", fmt, '*'); +#endif } - +#if 0 template void format_test_floating_point_hex_lower_case(TestFunction check) { auto nan_pos = std::numeric_limits::quiet_NaN(); // "nan" @@ -2370,8 +2408,10 @@ format_test_floating_point_default_precision(check); // *** type *** +# ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK for (const auto& fmt : invalid_types("aAeEfFgG")) check_exception("The format-spec type has a type not supported for a floating-point argument", fmt, F(1)); +# endif } template @@ -2411,12 +2451,14 @@ check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), P(nullptr)); // *** type *** +# ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK for (const auto& fmt : invalid_types("p")) check_exception("The format-spec type has a type not supported for a pointer argument", fmt, P(nullptr)); +# endif } - -template -void format_test_handle(TestFunction check, ExceptionTest check_exception) { +#endif +template +void format_test_handle(CHECK_FUNCTION_ARGS) { // *** Valid permuatations *** check(SV("answer is '0xaaaa'"), SV("answer is '{}'"), status::foo); check(SV("answer is '0xaaaa'"), SV("answer is '{:x}'"), status::foo); @@ -2434,19 +2476,28 @@ check(SV("answer is 'foobar'"), SV("answer is '{:s}'"), status::foobar); // *** type *** +#ifndef TEST_FORMAT_USE_COMPILE_TIME_CHECK for (const auto& fmt : invalid_types("xXs")) check_exception("The format-spec type has a type not supported for a status argument", fmt, status::foo); +#endif } - +#if 0 template void format_test_pointer(TestFunction check, ExceptionTest check_exception) { format_test_pointer(check, check_exception); format_test_pointer(check, check_exception); format_test_pointer(check, check_exception); + + // *** type *** + for (const auto& fmt : invalid_types("bBcdoxX")) + check_ill_formed( + "The format-spec type has a type not supported for a char argument", + std::basic_string_view(fmt), '*'); } +#endif -template -void format_tests(TestFunction check, ExceptionTest check_exception) { +template +void format_tests(CHECK_FUNCTION_ARGS) { // *** Test escaping *** check(SV("{"), SV("{{")); check(SV("}"), SV("}}")); @@ -2456,26 +2507,26 @@ check(SV("hello true false"), SV("hello {1:} {0:}"), false, true); // ** Test invalid format strings *** - check_exception("The format string terminates at a '{'", SV("{")); - check_exception("The replacement field misses a terminating '}'", SV("{:"), 42); - - check_exception("The format string contains an invalid escape sequence", SV("}")); - check_exception("The format string contains an invalid escape sequence", SV("{:}-}"), 42); + check_ill_formed("The format string terminates at a '{'", SV("{")); + check_ill_formed("The replacement field misses a terminating '}'", SV("{:"), 42); + check_ill_formed("The format string contains an invalid escape sequence", SV("}")); + check_ill_formed("The format string contains an invalid escape sequence", SV("{:}-}"), 42); - check_exception("The format string contains an invalid escape sequence", SV("} ")); + check_ill_formed("The format string contains an invalid escape sequence", SV("} ")); - check_exception("The arg-id of the format-spec starts with an invalid character", SV("{-"), 42); - check_exception("Argument index out of bounds", SV("hello {}")); - check_exception("Argument index out of bounds", SV("hello {0}")); - check_exception("Argument index out of bounds", SV("hello {1}"), 42); + check_ill_formed("The arg-id of the format-spec starts with an invalid character", SV("{-"), 42); + check_ill_formed("Argument index out of bounds", SV("hello {}")); + check_ill_formed("Argument index out of bounds", SV("hello {0}")); + check_ill_formed("Argument index out of bounds", SV("hello {1}"), 42); // *** Test char format argument *** // The `char` to `wchar_t` formatting is tested separately. check(SV("hello 09azAZ!"), SV("hello {}{}{}{}{}{}{}"), CharT('0'), CharT('9'), CharT('a'), CharT('z'), CharT('A'), CharT('Z'), CharT('!')); - format_test_char(check, check_exception); - format_test_char_as_integer(check, check_exception); + CALL_CHECK_FUNCTION(format_test_char); + + CALL_CHECK_FUNCTION(format_test_char_as_integer); // *** Test string format argument *** { @@ -2497,8 +2548,8 @@ std::basic_string_view data = buffer; check(SV("hello world"), SV("hello {}"), data); } - format_string_tests(check, check_exception); - +// CALL_CHECK_FUNCTION(format_string_tests); +#if 0 // *** Test Boolean format argument *** check(SV("hello false true"), SV("hello {} {}"), false, true); @@ -2511,7 +2562,7 @@ check(SV("hello 42"), SV("hello {}"), static_cast(42)); check(SV("hello 42"), SV("hello {}"), static_cast(42)); check(SV("hello 42"), SV("hello {}"), static_cast(42)); -#ifndef TEST_HAS_NO_INT128 +# ifndef TEST_HAS_NO_INT128 check(SV("hello 42"), SV("hello {}"), static_cast<__int128_t>(42)); { // Note 128-bit support is only partly implemented test the range @@ -2525,7 +2576,7 @@ check_exception("128-bit value is outside of implemented range", SV("{}"), static_cast<__int128_t>(std::numeric_limits::max()) + 1); } -#endif +# endif format_test_signed_integer(check, check_exception); // ** Test unsigned integral format argument *** @@ -2534,7 +2585,7 @@ check(SV("hello 42"), SV("hello {}"), static_cast(42)); check(SV("hello 42"), SV("hello {}"), static_cast(42)); check(SV("hello 42"), SV("hello {}"), static_cast(42)); -#ifndef TEST_HAS_NO_INT128 +# ifndef TEST_HAS_NO_INT128 check(SV("hello 42"), SV("hello {}"), static_cast<__uint128_t>(42)); { // Note 128-bit support is only partly implemented test the range @@ -2545,7 +2596,7 @@ check_exception("128-bit value is outside of implemented range", SV("{}"), static_cast<__uint128_t>(std::numeric_limits::max()) + 1); } -#endif +# endif format_test_unsigned_integer(check, check_exception); // *** Test floating point format argument *** @@ -2559,17 +2610,26 @@ check(SV("hello 0x42"), SV("hello {}"), reinterpret_cast(0x42)); check(SV("hello 0x42"), SV("hello {}"), reinterpret_cast(0x42)); format_test_pointer(check, check_exception); - +#endif // *** Test handle formatter argument *** - format_test_handle(check, check_exception); + CALL_CHECK_FUNCTION(format_test_handle); } #ifndef TEST_HAS_NO_WIDE_CHARACTERS +# ifdef TEST_FORMAT_USE_COMPILE_TIME_CHECK +void format_tests_char_to_wchar_t() +# else template -void format_tests_char_to_wchar_t(TestFunction check) { +void format_tests_char_to_wchar_t(TestFunction check) +# endif +{ using CharT = wchar_t; check(SV("hello 09azA"), SV("hello {}{}{}{}{}"), '0', '9', 'a', 'z', 'A'); } #endif +#undef CALL_CHECK_FUNCTION +#undef CHECK_FUNCTION_ARGS +#undef CHECK_TEMPLATE_ARGS + #endif diff --git a/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp b/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp --- a/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp @@ -29,61 +29,72 @@ #include #include "test_macros.h" -#include "format_tests.h" -auto test = [](std::basic_string_view expected, std::basic_string_view fmt, - const Args&... args) { - { - std::basic_string out(expected.size(), CharT(' ')); - auto it = std::format_to(out.begin(), fmt, args...); - assert(it == out.end()); - assert(out == expected); - } - { - std::list out; - std::format_to(std::back_inserter(out), fmt, args...); - assert(std::equal(out.begin(), out.end(), expected.begin(), expected.end())); - } - { - std::vector out; - std::format_to(std::back_inserter(out), fmt, args...); - assert(std::equal(out.begin(), out.end(), expected.begin(), expected.end())); - } - { - assert(expected.size() < 4096 && "Update the size of the buffer."); - CharT out[4096]; - CharT* it = std::format_to(out, fmt, args...); - assert(std::distance(out, it) == int(expected.size())); - // Convert to std::string since output contains '\0' for boolean tests. - assert(std::basic_string(out, it) == expected); - } -}; +#ifdef _LIBCPP_VERSION +# define test_basic_format_string std::__basic_format_string +#else +# error "Please add a define for your platform" +#endif + +#define check(e, fmt, ...) \ + { \ + /* Force the proper type for expected. */ \ + /* When using a check function its argument does the conversion, here do it explicitly. */ \ + std::basic_string_view expected = e; \ + { \ + std::basic_string out(expected.size(), CharT(' ')); \ + auto it = std::format_to(out.begin(), fmt __VA_OPT__(, __VA_ARGS__)); \ + assert(it == out.end()); \ + assert(out == expected); \ + } \ + { \ + std::list out; \ + std::format_to(std::back_inserter(out), fmt __VA_OPT__(, __VA_ARGS__)); \ + assert(std::equal(out.begin(), out.end(), expected.begin(), expected.end())); \ + } \ + { \ + std::vector out; \ + std::format_to(std::back_inserter(out), fmt __VA_OPT__(, __VA_ARGS__)); \ + assert(std::equal(out.begin(), out.end(), expected.begin(), expected.end())); \ + } \ + { \ + assert(expected.size() < 4096 && "Update the size of the buffer."); \ + CharT out[4096]; \ + CharT* it = std::format_to(out, fmt __VA_OPT__(, __VA_ARGS__)); \ + assert(std::distance(out, it) == int(expected.size())); \ + /* Convert to std::string since output contains '\0' for boolean tests. */ \ + assert(std::basic_string(out, it) == expected); \ + } \ + } \ + /* */ -auto test_exception = [](std::string_view what, std::basic_string_view fmt, - const Args&... args) { -#ifndef TEST_HAS_NO_EXCEPTIONS - try { - std::basic_string out; - std::format_to(std::back_inserter(out), fmt, args...); - assert(false); - } catch (const std::format_error& e) { - LIBCPP_ASSERT(e.what() == what); - return; - } - assert(false); +#ifdef TEST_HAS_NO_EXCEPTIONS +# define check_exception(...) #else - (void)what; - (void)fmt; - (void)sizeof...(args); +# define check_exception(what, fmt, ...) \ + { \ + try { \ + std::basic_string out; \ + std::format_to(std::back_inserter(out), fmt __VA_OPT__(, __VA_ARGS__)); \ + assert(false); \ + } catch (std::format_error & e) { \ + LIBCPP_ASSERT(e.what() == what); \ + return; \ + } \ + assert(false); \ + } \ + /* */ #endif -}; + +#define TEST_FORMAT_USE_COMPILE_TIME_CHECK 1 +#include "format_tests.h" int main(int, char**) { - format_tests(test, test_exception); + format_tests(); #ifndef TEST_HAS_NO_WIDE_CHARACTERS - format_tests_char_to_wchar_t(test); - format_tests(test, test_exception); + format_tests_char_to_wchar_t(); + format_tests(); #endif return 0;