From 193841ef4d63799162d435e90f3bd1d2eacea42a Mon Sep 17 00:00:00 2001 From: Mark de Wever Date: Fri, 14 Feb 2025 18:38:51 +0100 Subject: [PATCH] [libc++][format] Improves diagnostics. When attempting to format a type that does not have a valid formatter specialization the code is ill-formed and diagnostics are generated. The patch now properly constrains the exposition-only basic_format_arg constructor (LWG3246). This constructor is called by - std::make_format_args. This function has the precondition The type typename Context::template formatter_type> meets the BasicFormatter requirements ([formatter.requirements]) for each Ti in Args. - In libc++ `std::basic_format_string` constructor too. This is not specified by the standard, however the requirements for constructor allow this. Since `basic_format_arg` exposition-only constructor's constraint can't be observed by the user, they can use it to SFINAE their own code. Currently libc++, libstdc++, and MSVC STL consider the code ill-formed. This patch keeps that behaviour. This patch improves the output of these diagnostics: - Reduces the number of errors generated, which reduces the number of lines in the output. - The error gives a more specific diagnotic in multiple cases. For example, the formatter's format function must be const qualified. The diagnostic now mentions this specific issue. Summary of the error messages using the type no_formatter_specialization, which as the name implies has no specialization. std::format (and friends) Before - 163 lines of output with 7 errors - First two diagnostics: - error: static assertion failed due to requirement '__arg != __arg_t::__none': the supplied type is not formattable - note: in instantiation of function template specialization 'std::__format::__create_format_arg' requested here After - 25 lines of output with 1 error - First two diagnostics: - error: static assertion failed: The required formatter specialization has not been provided. - note: in instantiation of function template specialization 'std::__format::__diagnose_invalid_formatter' requested here std::make_format_args (used by users to call vformat and friends) Before - 70 lines of output with 3 errors - First two diagnostics: - error: static assertion failed due to requirement '__arg != __arg_t::__none': the supplied type is not formattable - note: in instantiation of function template specialization 'std::__format::__create_format_arg' requested here After - 25 lines of output with 1 error - First two diagnostics: - error: static assertion failed: The required formatter specialization has not been provided. - note: in instantiation of function template specialization 'std::__format::__diagnose_invalid_formatter' requested here Fixes: #100263 --- libcxx/include/__format/format_arg.h | 19 +- libcxx/include/__format/format_arg_store.h | 150 ++++++- libcxx/include/__format/format_context.h | 8 +- libcxx/include/__format/format_functions.h | 31 ++ .../make_format_args.verify.cpp | 206 +++++++++ .../format/format.functions/format.verify.cpp | 411 ++++++++++++++++++ 6 files changed, 809 insertions(+), 16 deletions(-) create mode 100644 libcxx/test/libcxx/utilities/format/format.arguments/format.arg.store/make_format_args.verify.cpp create mode 100644 libcxx/test/libcxx/utilities/format/format.functions/format.verify.cpp diff --git a/libcxx/include/__format/format_arg.h b/libcxx/include/__format/format_arg.h index 10f0ba9928ce7..9f399f709f570 100644 --- a/libcxx/include/__format/format_arg.h +++ b/libcxx/include/__format/format_arg.h @@ -219,7 +219,7 @@ class __basic_format_arg_value { __format_([](basic_format_parse_context<_CharT>& __parse_ctx, _Context& __ctx, const void* __ptr) { using _Dp = remove_const_t<_Tp>; using _Qp = conditional_t<__formattable_with, const _Dp, _Dp>; - static_assert(__formattable_with<_Qp, _Context>, "Mandated by [format.arg]/10"); + static_assert(__formattable_with<_Qp, _Context>, "Mandated by [format.arg]/12"); typename _Context::template formatter_type<_Dp> __f; __parse_ctx.advance_to(__f.parse(__parse_ctx)); @@ -334,21 +334,16 @@ class _LIBCPP_TEMPLATE_VIS _LIBCPP_NO_SPECIALIZATIONS basic_format_arg { private: using char_type = typename _Context::char_type; - // TODO FMT Implement constrain [format.arg]/4 - // Constraints: The template specialization - // typename Context::template formatter_type - // meets the Formatter requirements ([formatter.requirements]). The extent - // to which an implementation determines that the specialization meets the - // Formatter requirements is unspecified, except that as a minimum the - // expression - // typename Context::template formatter_type() - // .format(declval(), declval()) - // shall be well-formed when treated as an unevaluated operand. - public: __basic_format_arg_value<_Context> __value_; __format::__arg_t __type_; + // This constructor is used for the exposition only constructor. + // Per [format.arg]/4 + // template explicit basic_format_arg(T& v) noexcept; + // Constraints: T satisfies formattable-with. + // + // This constraint is implemented in __create_format_arg _LIBCPP_HIDE_FROM_ABI explicit basic_format_arg(__format::__arg_t __type, __basic_format_arg_value<_Context> __value) noexcept : __value_(__value), __type_(__type) {} diff --git a/libcxx/include/__format/format_arg_store.h b/libcxx/include/__format/format_arg_store.h index c6c7fdeedcfe6..711736ff19c4f 100644 --- a/libcxx/include/__format/format_arg_store.h +++ b/libcxx/include/__format/format_arg_store.h @@ -16,11 +16,15 @@ #include <__concepts/arithmetic.h> #include <__concepts/same_as.h> +#include <__concepts/semiregular.h> #include <__config> #include <__format/concepts.h> #include <__format/format_arg.h> +#include <__format/format_parse_context.h> #include <__type_traits/conditional.h> #include <__type_traits/extent.h> +#include <__type_traits/is_assignable.h> +#include <__type_traits/is_constructible.h> #include <__type_traits/remove_const.h> #include #include @@ -161,12 +165,11 @@ consteval __arg_t __determine_arg_t() { // // Modeled after template explicit basic_format_arg(T& v) noexcept; // [format.arg]/4-6 -template +template _Tp> _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __value) noexcept { using _Dp = remove_const_t<_Tp>; constexpr __arg_t __arg = __format::__determine_arg_t<_Context, _Dp>(); static_assert(__arg != __arg_t::__none, "the supplied type is not formattable"); - static_assert(__formattable_with<_Tp, _Context>); // Not all types can be used to directly initialize the // __basic_format_arg_value. First handle all types needing adjustment, the @@ -205,6 +208,149 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu return basic_format_arg<_Context>{__arg, __value}; } +// Helper function that issues a diagnostic when __formattable_with<_Tp, _Context> is false. +// +// Since it's quite easy to make a mistake writing a formatter specialization +// this function tries to give a better explanation. This should improve the +// diagnostics when trying to format type that has no properly specialized +// formatter. +template +[[noreturn]] _LIBCPP_HIDE_FROM_ABI constexpr void __diagnose_invalid_formatter() { + using _Formatter = typename _Context::template formatter_type>; + constexpr bool __is_disabled = + !is_default_constructible_v<_Formatter> && !is_copy_constructible_v<_Formatter> && + !is_move_constructible_v<_Formatter> && !is_copy_assignable_v<_Formatter> && !is_move_assignable_v<_Formatter>; + constexpr bool __is_semiregular = semiregular<_Formatter>; + + constexpr bool __has_parse_function = + requires(_Formatter& __f, basic_format_parse_context __pc) { + { __f.parse(__pc) }; + }; + constexpr bool __correct_parse_function_return_type = + requires(_Formatter& __f, basic_format_parse_context __pc) { + { __f.parse(__pc) } -> same_as; + }; + + // The reason these static_asserts are placed in an if-constexpr-chain is to + // only show one error. For example, when the formatter is not specialized it + // would show all static_assert messages. With this chain the compiler only + // evaluates one static_assert. + + if constexpr (__is_disabled) + + static_assert( +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(_Tp) == 0 +# else + false +# endif + , + "The required formatter specialization has not been provided."); + else if constexpr (!__is_semiregular) + static_assert( +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(_Tp) == 0 +# else + false +# endif + , + "The required formatter specialization is not semiregular."); + + else if constexpr (!__has_parse_function) + static_assert( +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(_Tp) == 0 +# else + false +# endif + , + "The required formatter specialization does not have a parse function taking the proper arguments."); + else if constexpr (!__correct_parse_function_return_type) + static_assert( +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(_Tp) == 0 +# else + false +# endif + , + "The required formatter specialization's parse function does not return the required type."); + + else { + // During constant evaluation this function is called, but the format + // member function has not been evaluated. This means these functions + // can't be evaluated at that time. + // + // Note this else branch should never been taken during constant + // eveluation, the static_asserts in one of the branches above should + // trigger. + constexpr bool __has_format_function = requires(_Formatter& __f, _Tp&& __t, _Context __fc) { + { __f.format(__t, __fc) }; + }; + constexpr bool __is_format_function_const_qualified = requires(const _Formatter& __cf, _Tp&& __t, _Context __fc) { + { __cf.format(__t, __fc) }; + }; + constexpr bool __correct_format_function_return_type = requires(_Formatter& __f, _Tp&& __t, _Context __fc) { + { __f.format(__t, __fc)->template same_as }; + }; + + if constexpr (!__has_format_function) + static_assert( +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(_Tp) == 0 +# else + false +# endif + , + "The required formatter specialization does not have a format function taking the proper arguments."); + else if constexpr (!__is_format_function_const_qualified) + static_assert( +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(_Tp) == 0 +# else + false +# endif + , + "The required formatter specialization's format function is not const qualified."); + else if constexpr (!__correct_format_function_return_type) + static_assert( +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(_Tp) == 0 +# else + false +# endif + , + "The required formatter specialization's format function does not return the required type."); + + else + // This should not happen; it makes sure the code is ill-formed. + static_assert( +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(_Tp) == 0 +# else + false +# endif + , + "The required formatter specialization is not formattable with its context."); + } +} + +// The Psuedo constructor is constrained per [format.arg]/4. +// The only way for a user to call __create_format_arg is from std::make_format_args. +// Per [format.arg.store]/3 +// template +// format-arg-store make_format_args(Args&... fmt_args); +// +// Preconditions: The type typename Context::template formatter_type> +// meets the BasicFormatter requirements ([formatter.requirements]) for each Ti in Args. +// +// This function is only called when the precondition is violated. +// Without this function the compilation would fail since the overload is +// SFINAE'ed away. This function is used to provide better diagnostics. +template +_LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp&) noexcept { + __format::__diagnose_invalid_formatter<_Context, _Tp>(); +} + template _LIBCPP_HIDE_FROM_ABI void __create_packed_storage(uint64_t& __types, __basic_format_arg_value<_Context>* __values, _Args&... __args) noexcept { diff --git a/libcxx/include/__format/format_context.h b/libcxx/include/__format/format_context.h index 4dbfdbc02a267..11a4c427bcbc2 100644 --- a/libcxx/include/__format/format_context.h +++ b/libcxx/include/__format/format_context.h @@ -65,9 +65,13 @@ __format_context_create(_OutIt __out_it, basic_format_args>, char>; +template +using __format_context _LIBCPP_NODEBUG = + basic_format_context>, _CharT>; + +using format_context = __format_context; # if _LIBCPP_HAS_WIDE_CHARACTERS -using wformat_context = basic_format_context< back_insert_iterator<__format::__output_buffer>, wchar_t>; +using wformat_context = __format_context; # endif template diff --git a/libcxx/include/__format/format_functions.h b/libcxx/include/__format/format_functions.h index 5feaf7e5a064a..bea2ff0387eed 100644 --- a/libcxx/include/__format/format_functions.h +++ b/libcxx/include/__format/format_functions.h @@ -13,6 +13,7 @@ #include <__algorithm/clamp.h> #include <__concepts/convertible_to.h> #include <__concepts/same_as.h> +#include <__concepts/semiregular.h> #include <__config> #include <__format/buffer.h> #include <__format/format_arg.h> @@ -78,6 +79,27 @@ template namespace __format { +/// A "watered-down" __formattable_with for usage during constant evaluation. +/// +/// The member function "format" can have a decuded return type. When +/// constraining the __compile_time_handle this leads to a nested concept +/// eveluation that is always false. Instead ignore the "format" member function. +/// +/// In theory the same could happen to the "parse" member function. However +/// thisfunction does not is less likely to have odd cased, and this function +/// is "required" to be called during constant evaluation. So if this +/// function's return type needs to be deduced things will probably not work. +/// +/// See https://github.com/llvm/llvm-project/issues/81590 and +/// test/std/utilities/format/format.functions/bug_81590.compile.pass.cpp for +/// an example of a problematic "format" member function. +template >> +concept __formattable_with_constexpr = + semiregular<_Formatter> && + requires(_Formatter& __f, _Tp&& __t, basic_format_parse_context __pc) { + { __f.parse(__pc) } -> same_as; + }; + /// Helper class parse and handle argument. /// /// When parsing a handle which is not enabled the code is ill-formed. @@ -91,6 +113,7 @@ class _LIBCPP_TEMPLATE_VIS __compile_time_handle { } template + requires __formattable_with_constexpr<_Tp, __format_context<_CharT>> _LIBCPP_HIDE_FROM_ABI constexpr void __enable() { __parse_ = [](basic_format_parse_context<_CharT>& __ctx) { formatter<_Tp, _CharT> __f; @@ -98,6 +121,14 @@ class _LIBCPP_TEMPLATE_VIS __compile_time_handle { }; } + template + _LIBCPP_HIDE_FROM_ABI constexpr void __enable() { + // Avoids the throw set in the constructor. + // Otherwise two diagnostics will be issued. + __parse_ = [](basic_format_parse_context<_CharT>&) {}; + __format::__diagnose_invalid_formatter<__format_context<_CharT>, _Tp>(); + } + // Before calling __parse the proper handler needs to be set with __enable. // The default handler isn't a core constant expression. _LIBCPP_HIDE_FROM_ABI constexpr __compile_time_handle() diff --git a/libcxx/test/libcxx/utilities/format/format.arguments/format.arg.store/make_format_args.verify.cpp b/libcxx/test/libcxx/utilities/format/format.arguments/format.arg.store/make_format_args.verify.cpp new file mode 100644 index 0000000000000..adcd9ac115abb --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/format.arguments/format.arg.store/make_format_args.verify.cpp @@ -0,0 +1,206 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// format-arg-store make_format_args(Args&... args); +// +// Preconditions: The type typename Context::template formatter_type> +// meets the BasicFormatter requirements ([formatter.requirements]) for each Ti in Args. +// +// When the precondition is violated libc++ diagnoses the issue with the +// formatter specialization. + +// Apple-clang-15 does not support static_assert(false). Instead the code use +// static_assert(sizof(T) == 0, ...). This gives different diagnostics. +// Since the compiler is no longer the latest XCode version it makes little +// sense to add work-arounds. +// XFAIL: apple-clang-15 + +#include + +#include "test_macros.h" + +struct no_formatter_specialization {}; +void test_no_formatter_specialization() { + no_formatter_specialization t; + // expected-error@*:* {{static assertion failed: The required formatter specialization has not been provided.}} + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization has not been provided.}} + (void)std::make_wformat_args(t); +#endif +} + +struct correct_formatter_specialization {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext&); + + template + typename FormatContext::iterator format(correct_formatter_specialization&, FormatContext&) const; +}; +void test_correct_formatter_specialization() { + correct_formatter_specialization t; + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + (void)std::make_wformat_args(t); +#endif +} + +struct formatter_not_semiregular {}; +template +struct std::formatter { + formatter(int); + + template + constexpr typename ParseContext::iterator parse(ParseContext&); + + template + typename FormatContext::iterator format(formatter_not_semiregular&, FormatContext&) const; +}; +void test_formatter_not_semiregular() { + formatter_not_semiregular t; + // expected-error@*:* {{static assertion failed: The required formatter specialization is not semiregular.}} + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization is not semiregular.}} + (void)std::make_wformat_args(t); +#endif +} + +struct formatter_no_parse_function {}; +template +struct std::formatter { + template + typename FormatContext::iterator format(formatter_no_parse_function&, FormatContext&) const; +}; +void test_formatter_no_parse_function() { + formatter_no_parse_function t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::make_wformat_args(t); +#endif +} + +struct parse_function_invalid_arguments {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext&, int); + + template + typename FormatContext::iterator format(parse_function_invalid_arguments&, FormatContext&) const; +}; +void test_parse_function_invalid_arguments() { + parse_function_invalid_arguments t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::make_wformat_args(t); +#endif +} + +struct parse_function_invalid_return_type {}; +template +struct std::formatter { + template + constexpr int parse(ParseContext&); + + template + typename FormatContext::iterator format(parse_function_invalid_return_type&, FormatContext&) const; +}; +void test_parse_function_invalid_return_type() { + parse_function_invalid_return_type t; + // expected-error@*:* {{static assertion failed: The required formatter specialization's parse function does not return the required type.}} + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization's parse function does not return the required type.}} + (void)std::make_wformat_args(t); +#endif +} + +struct no_format_function {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext&); +}; +void test_no_format_function() { + no_format_function t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + (void)std::make_wformat_args(t); +#endif +} + +struct format_function_invalid_arguments {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext&); + + template + typename FormatContext::iterator format(format_function_invalid_arguments&) const; +}; +void test_format_function_invalid_arguments() { + format_function_invalid_arguments t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + (void)std::make_wformat_args(t); +#endif +} + +struct format_function_invalid_return_type {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext&); + + template + int format(format_function_invalid_return_type&, FormatContext&) const; +}; +void test_format_function_invalid_return_type() { + format_function_invalid_return_type t; + // expected-error@*:* {{static assertion failed: The required formatter specialization's format function does not return the required type.}} + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization's format function does not return the required type.}} + (void)std::make_wformat_args(t); +#endif +} + +struct format_function_not_const_qualified {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext&); + + template + typename FormatContext::iterator format(format_function_not_const_qualified&, FormatContext&); +}; +void test_format_function_not_const_qualified() { + format_function_not_const_qualified t; + // expected-error@*:* {{static assertion failed: The required formatter specialization's format function is not const qualified.}} + (void)std::make_format_args(t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization's format function is not const qualified.}} + (void)std::make_wformat_args(t); +#endif +} diff --git a/libcxx/test/libcxx/utilities/format/format.functions/format.verify.cpp b/libcxx/test/libcxx/utilities/format/format.functions/format.verify.cpp new file mode 100644 index 0000000000000..6d9347a9d828d --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/format.functions/format.verify.cpp @@ -0,0 +1,411 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// When the fmt is not a format string for Args the expression is not a core +// constant exprssion. In this case the code is ill-formed. +// When this happens due to issues with the formatter specialization libc++ +// diagnoses the issue. + +// Apple-clang-15 does not support static_assert(false). Instead the code use +// static_assert(sizof(T) == 0, ...). This gives different diagnostics. +// Since the compiler is no longer the latest XCode version it makes little +// sense to add work-arounds. +// XFAIL: apple-clang-15 + +#include +#include + +#include "test_macros.h" + +struct no_formatter_specialization {}; +void test_no_formatter_specialization() { + no_formatter_specialization t; + // expected-error@*:* {{static assertion failed: The required formatter specialization has not been provided.}} + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization has not been provided.}} + (void)std::format(L"{}", t); +#endif +} + +struct correct_formatter_specialization {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext& ctx) { + return ctx.begin(); + } + + template + typename FormatContext::iterator format(correct_formatter_specialization&, FormatContext& ctx) const { + return ctx.out(); + } +}; +void test_correct_formatter_specialization() { + correct_formatter_specialization t; + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + (void)std::format(L"{}", t); +#endif +} + +struct formatter_not_semiregular {}; +template +struct std::formatter { + formatter(int); + + template + constexpr typename ParseContext::iterator parse(ParseContext& ctx) { + return ctx.begin(); + } + + template + typename FormatContext::iterator format(formatter_not_semiregular&, FormatContext&) const; +}; +void test_formatter_not_semiregular() { + formatter_not_semiregular t; + // expected-error@*:* {{static assertion failed: The required formatter specialization is not semiregular.}} + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization is not semiregular.}} + (void)std::format(L"{}", t); +#endif +} + +struct formatter_no_parse_function {}; +template +struct std::formatter { + template + typename FormatContext::iterator format(formatter_no_parse_function&, FormatContext&) const; +}; +void test_formatter_no_parse_function() { + formatter_no_parse_function t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::format(L"{}", t); +#endif +} + +struct parse_function_invalid_arguments {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext& ctx, int) { + return ctx.begin(); + } + + template + typename FormatContext::iterator format(parse_function_invalid_arguments&, FormatContext&) const; +}; +void test_parse_function_invalid_arguments() { + parse_function_invalid_arguments t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::format(L"{}", t); +#endif +} + +struct parse_function_invalid_return_type {}; +template +struct std::formatter { + template + constexpr int parse(ParseContext&) { + return 42; + } + + template + typename FormatContext::iterator format(parse_function_invalid_return_type&, FormatContext&) const; +}; +void test_parse_function_invalid_return_type() { + parse_function_invalid_return_type t; + // expected-error@*:* {{static assertion failed: The required formatter specialization's parse function does not return the required type.}} + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization's parse function does not return the required type.}} + (void)std::format(L"{}", t); +#endif +} + +struct no_format_function {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext& ctx) { + return ctx.begin(); + } +}; +void test_no_format_function() { + no_format_function t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + (void)std::format(L"{}", t); +#endif +} + +struct format_function_invalid_arguments {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext& ctx) { + return ctx.begin(); + } + + template + typename FormatContext::iterator format(format_function_invalid_arguments&) const; +}; +void test_format_function_invalid_arguments() { + format_function_invalid_arguments t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + (void)std::format(L"{}", t); +#endif +} + +struct format_function_invalid_return_type {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext& ctx) { + return ctx.begin(); + } + + template + int format(format_function_invalid_return_type&, FormatContext&) const; +}; +void test_format_function_invalid_return_type() { + format_function_invalid_return_type t; + // expected-error@*:* {{static assertion failed: The required formatter specialization's format function does not return the required type.}} + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization's format function does not return the required type.}} + (void)std::format(L"{}", t); +#endif +} + +struct format_function_not_const_qualified {}; +template +struct std::formatter { + template + constexpr typename ParseContext::iterator parse(ParseContext& ctx) { + return ctx.begin(); + } + + template + typename FormatContext::iterator format(format_function_not_const_qualified&, FormatContext&); +}; +void test_format_function_not_const_qualified() { + format_function_not_const_qualified t; + // expected-error@*:* {{static assertion failed: The required formatter specialization's format function is not const qualified.}} + (void)std::format("{}", t); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // expected-error@*:* {{static assertion failed: The required formatter specialization's format function is not const qualified.}} + (void)std::format(L"{}", t); +#endif +} + +struct auto_deduction_correct_formatter_specialization + : std::variant { + auto_deduction_correct_formatter_specialization* p = nullptr; + constexpr const std::variant& decay() const noexcept { + return *this; + } +}; + +template <> +struct std::formatter { + template + static constexpr auto parse(ParseContext& ctx) { + return ctx.begin(); + } + static constexpr auto format(const auto_deduction_correct_formatter_specialization& x, auto& ctx) { + if (!x.p) + return ctx.out(); + auto m = [&](const auto_deduction_correct_formatter_specialization* t) { + return std::format_to(ctx.out(), "{}", *t); + }; + return std::visit(m, x.decay()); + } +}; + +void test_auto_deduction_correct_formatter_specialization() { + auto_deduction_correct_formatter_specialization t; + (void)std::format("{}", t); +} + +struct auto_deduction_no_parse_function : std::variant { + auto_deduction_no_parse_function* p = nullptr; + constexpr const std::variant& decay() const noexcept { return *this; } +}; + +template <> +struct std::formatter { + static constexpr auto format(const auto_deduction_no_parse_function& x, auto& ctx) { + if (!x.p) + return ctx.out(); + auto m = [&](const auto_deduction_no_parse_function* t) { return std::format_to(ctx.out(), "{}", *t); }; + return std::visit(m, x.decay()); + } +}; + +void test_auto_deduction_no_parse_function() { + auto_deduction_no_parse_function t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::format("{}", t); +} + +struct auto_deduction_parse_function_invalid_arguments + : std::variant { + auto_deduction_parse_function_invalid_arguments* p = nullptr; + constexpr const std::variant& decay() const noexcept { + return *this; + } +}; + +template <> +struct std::formatter { + template + static constexpr auto parse(ParseContext& ctx, int) { + return ctx.begin(); + } + static constexpr auto format(const auto_deduction_parse_function_invalid_arguments& x, auto& ctx) { + if (!x.p) + return ctx.out(); + auto m = [&](const auto_deduction_parse_function_invalid_arguments* t) { + return std::format_to(ctx.out(), "{}", *t); + }; + return std::visit(m, x.decay()); + } +}; + +void test_auto_deduction_parse_function_invalid_arguments() { + auto_deduction_parse_function_invalid_arguments t; + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a parse function taking the proper arguments.}} + (void)std::format("{}", t); +} + +struct auto_deduction_parse_function_invalid_return_types + : std::variant { + auto_deduction_parse_function_invalid_return_types* p = nullptr; + constexpr const std::variant& decay() const noexcept { + return *this; + } +}; + +template <> +struct std::formatter { + template + static constexpr auto parse(ParseContext&) { + return 42; + } + static constexpr auto format(const auto_deduction_parse_function_invalid_return_types& x, auto& ctx) { + if (!x.p) + return ctx.out(); + auto m = [&](const auto_deduction_parse_function_invalid_return_types* t) { + return std::format_to(ctx.out(), "{}", *t); + }; + return std::visit(m, x.decay()); + } +}; + +void test_auto_deduction_parse_function_invalid_return_types() { + // expected-error@*:* {{static assertion failed: The required formatter specialization's parse function does not return the required type.}} + auto_deduction_parse_function_invalid_return_types t; + (void)std::format("{}", t); +} + +struct auto_deduction_no_format_function : std::variant { + auto_deduction_no_format_function* p = nullptr; + // expected-error@*:* 2 {{static assertion failed: The required formatter specialization's format function does not return the required type.}} + constexpr const std::variant& decay() const noexcept { return *this; } +}; + +template <> +struct std::formatter { + template + static constexpr auto parse(ParseContext& ctx) { + return ctx.begin(); + } +}; + +void test_auto_deduction_no_format_function() { + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + auto_deduction_no_format_function t; + (void)std::format("{}", t); +} + +struct auto_deduction_format_function_invalid_arguments + : std::variant { + auto_deduction_format_function_invalid_arguments* p = nullptr; + constexpr const std::variant& decay() const noexcept { + return *this; + } +}; + +template <> +struct std::formatter { + template + static constexpr auto parse(ParseContext& ctx) { + return ctx.begin(); + } + static constexpr auto format(const auto_deduction_format_function_invalid_arguments& x, auto& ctx, int) { + if (!x.p) + return ctx.out(); + auto m = [&](const auto_deduction_format_function_invalid_arguments* t) { + return std::format_to(ctx.out(), "{}", *t); + }; + return std::visit(m, x.decay()); + } +}; + +void test_auto_deduction_format_function_invalid_arguments() { + // expected-error@*:* {{static assertion failed: The required formatter specialization does not have a format function taking the proper arguments.}} + auto_deduction_format_function_invalid_arguments t; + (void)std::format("{}", t); +} + +struct auto_deduction_format_function_invalid_return_types + : std::variant { + auto_deduction_format_function_invalid_return_types* p = nullptr; + constexpr const std::variant& decay() const noexcept { + return *this; + } +}; + +template <> +struct std::formatter { + template + static constexpr auto parse(ParseContext& ctx) { + return ctx.begin(); + } + static constexpr auto format(const auto_deduction_format_function_invalid_return_types& x, auto& ctx) { + if (!x.p) + return 42; + auto m = [&](const auto_deduction_format_function_invalid_return_types* t) { + std::format_to(ctx.out(), "{}", *t); + return 42; + }; + return std::visit(m, x.decay()); + } +}; + +void test_auto_deduction_format_function_invalid_return_types() { + auto_deduction_format_function_invalid_return_types t; + (void)std::format("{}", t); +}