diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv --- a/libcxx/docs/Status/Cxx20Issues.csv +++ b/libcxx/docs/Status/Cxx20Issues.csv @@ -207,7 +207,7 @@ "`3247 `__","``ranges::iter_move``\ should perform ADL-only lookup of ``iter_move``\ ","Prague","","","|ranges|" "`3248 `__","``std::format``\ ``#b``\ , ``#B``\ , ``#o``\ , ``#x``\ , and ``#X``\ presentation types misformat negative numbers","Prague","|Complete|","14.0","|format|" "`3250 `__","``std::format``\ : ``#``\ (alternate form) for NaN and inf","Prague","|Complete|","14.0","|format|" -"`3251 `__","Are ``std::format``\ alignment specifiers applied to string arguments?","Prague","","","|format|" +"`3251 `__","Are ``std::format``\ alignment specifiers applied to string arguments?","Prague","|Complete|","14.0","|format|" "`3252 `__","Parse locale's aware modifiers for commands are not consistent with POSIX spec","Prague","","","|chrono|" "`3254 `__","Strike ``stop_token``\ 's ``operator!=``\ ","Prague","","" "`3255 `__","``span``\ 's ``array``\ constructor is too strict","Prague","|Complete|","" @@ -256,7 +256,7 @@ "`3334 `__","``basic_osyncstream``\ move assignment and destruction calls ``basic_syncbuf::emit()``\ twice","Prague","","" "`3335 `__","Resolve C++20 NB comments US 273 and GB 274","Prague","","","|ranges|" "`3338 `__","Rename ``default_constructible``\ to ``default_initializable``\ ","Prague","|Complete|","13.0" -"`3340 `__","Formatting functions should throw on argument/format string mismatch in |sect|\ [format.functions]","Prague","","","|format|" +"`3340 `__","Formatting functions should throw on argument/format string mismatch in |sect|\ [format.functions]","Prague","|Complete|","14.0","|format|" "`3346 `__","``pair``\ and ``tuple``\ copy and move constructor have backwards specification","Prague","","" "`3347 `__","``std::pair``\ now requires ``T``\ and ``U``\ to be less-than-comparable","Prague","","" "`3348 `__","``__cpp_lib_unwrap_ref``\ in wrong header","Prague","|Complete|","12.0" diff --git a/libcxx/docs/Status/Cxx2bIssues.csv b/libcxx/docs/Status/Cxx2bIssues.csv --- a/libcxx/docs/Status/Cxx2bIssues.csv +++ b/libcxx/docs/Status/Cxx2bIssues.csv @@ -84,7 +84,7 @@ `3533 `__,"Make ``base() const &`` consistent across iterator wrappers that supports ``input_iterators``","June 2021","","","|ranges|" `3536 `__,"Should ``chrono::from_stream()`` assign zero to duration for failure?","June 2021","","","|chrono|" `3539 `__,"``format_to`` must not copy models of ``output_iterator``","June 2021","","","|format|" -`3540 `__,"§[format.arg] There should be no const in ``basic_format_arg(const T* p)``","June 2021","","","|format|" +`3540 `__,"§[format.arg] There should be no const in ``basic_format_arg(const T* p)``","June 2021","|Complete|","14.0","|format|" `3541 `__,"``indirectly_readable_traits`` should be SFINAE-friendly for all types","June 2021","","","|ranges|" `3542 `__,"``basic_format_arg`` mishandles ``basic_string_view`` with custom traits","June 2021","|Complete|","14.0","|format|" `3543 `__,"Definition of when ``counted_iterators`` refer to the same sequence isn't quite right","June 2021","","","|ranges|" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -156,6 +156,7 @@ __format/formatter_integer.h __format/formatter_integral.h __format/formatter_string.h + __format/formatter_pointer.h __format/parser_std_format_spec.h __function_like.h __functional_base diff --git a/libcxx/include/__format/format_arg.h b/libcxx/include/__format/format_arg.h --- a/libcxx/include/__format/format_arg.h +++ b/libcxx/include/__format/format_arg.h @@ -242,7 +242,9 @@ explicit basic_format_arg(nullptr_t) noexcept : __ptr(nullptr), __type_(__format::__arg_t::__ptr) {} - // TODO FMT Implement the _Tp* constructor. + template + requires is_void_v<_Tp> _LIBCPP_HIDE_FROM_ABI explicit basic_format_arg(_Tp* __p) noexcept + : __ptr(__p), __type_(__format::__arg_t::__ptr) {} }; #endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) diff --git a/libcxx/include/__format/formatter_pointer.h b/libcxx/include/__format/formatter_pointer.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/formatter_pointer.h @@ -0,0 +1,91 @@ +// -*- 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_POINTER_H +#define _LIBCPP___FORMAT_FORMATTER_POINTER_H + +#include <__algorithm/copy.h> +#include <__availability> +#include <__config> +#include <__debug> +#include <__format/format_error.h> +#include <__format/format_fwd.h> +#include <__format/formatter.h> +#include <__format/formatter_integral.h> +#include <__format/parser_std_format_spec.h> +#include <__iterator/access.h> +#include <__nullptr> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +# if !defined(_LIBCPP_HAS_NO_CONCEPTS) + +namespace __format_spec { + +template <__formatter::__char_type _CharT> +class _LIBCPP_TEMPLATE_VIS __formatter_pointer : public __parser_pointer<_CharT> { +public: + _LIBCPP_HIDE_FROM_ABI auto format(const void* __ptr, auto& __ctx) -> decltype(__ctx.out()) { + _LIBCPP_ASSERT(this->__alignment != _Flags::_Alignment::__default, + "The call to parse should have updated the alignment"); + if (this->__width_needs_substitution()) + this->__substitute_width_arg_id(__ctx.arg(this->__width)); + + // This code looks a lot like the code to format a hexadecimal integral, + // but that code isn't public. Making that code public requires some + // refactoring. + // TODO FMT Remove code duplication. + char __buffer[2 + 2 * sizeof(uintptr_t)]; + __buffer[0] = '0'; + __buffer[1] = 'x'; + char* __last = __to_buffer(__buffer + 2, _VSTD::end(__buffer), reinterpret_cast(__ptr), 16); + + unsigned __size = __last - __buffer; + if (__size >= this->__width) + return _VSTD::copy(__buffer, __last, __ctx.out()); + + return __formatter::__write(__ctx.out(), __buffer, __last, __size, this->__width, this->__fill, this->__alignment); + } +}; + +} // namespace __format_spec + +// [format.formatter.spec]/2.4 +// For each charT, the pointer type specializations template<> +// - struct formatter; +// - template<> struct formatter; +// - template<> struct formatter; +template <__formatter::__char_type _CharT> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __format_spec::__formatter_pointer<_CharT> {}; +template <__formatter::__char_type _CharT> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __format_spec::__formatter_pointer<_CharT> {}; +template <__formatter::__char_type _CharT> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __format_spec::__formatter_pointer<_CharT> {}; + +# endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FORMAT_FORMATTER_POINTER_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 @@ -837,7 +837,108 @@ } }; -// TODO FMT Add a parser for pointer values. +/** + * The parser for the std-format-spec. + * + * This implements the parser for the pointer types. + * + * See @ref __parser_string. + */ +template +class _LIBCPP_TEMPLATE_VIS __parser_pointer : public __parser_width, // provides __width(|as_arg) + public __parser_fill_align<_CharT>, // provides __fill and uses __flags + public _Flags // provides __flags +{ +public: + using char_type = _CharT; + + _LIBCPP_HIDE_FROM_ABI constexpr __parser_pointer() { + // Implements LWG3612 Inconsistent pointer alignment in std::format. + // The issue's current status is "Tentatively Ready" and libc++ status is + // still experimental. + // + // TODO FMT Validate this with the final resolution of LWG3612. + this->__alignment = _Flags::_Alignment::__right; + } + + /** + * The low-level std-format-spec parse function. + * + * @pre __begin points at the beginning of the std-format-spec. This means + * directly after the ':'. + * @pre The std-format-spec parses the entire input, or the first unmatched + * character is a '}'. + * + * @returns The iterator pointing at the last parsed character. + */ + _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) { + auto __it = __parse(__parse_ctx); + __process_display_type(); + return __it; + } + +protected: + /** + * The low-level std-format-spec parse function. + * + * @pre __begin points at the beginning of the std-format-spec. This means + * directly after the ':'. + * @pre The std-format-spec parses the entire input, or the first unmatched + * character is a '}'. + * + * @returns The iterator pointing at the last parsed character. + */ + _LIBCPP_HIDE_FROM_ABI constexpr auto __parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) { + auto __begin = __parse_ctx.begin(); + auto __end = __parse_ctx.end(); + if (__begin == __end) + return __begin; + + __begin = __parser_fill_align<_CharT>::__parse(__begin, __end, static_cast<_Flags&>(*this)); + if (__begin == __end) + return __begin; + + // An integer presentation type isn't defined in the Standard. + // Since a pointer is formatted as an integer it can be argued it's an + // integer presentation type. However there are two LWG-issues asserting it + // isn't an integer presentation type: + // - LWG3612 Inconsistent pointer alignment in std::format + // - LWG3644 std::format does not define "integer presentation type" + // + // There's a paper to make additional clarifications on the status of + // formatting pointers and proposes additional fields to be valid. That + // paper hasn't been reviewed by the Committee yet. + // - P2510 Formatting pointers + // + // The current implementation assumes formatting pointers isn't covered by + // "integer presentation type". + // TODO FMT Apply the LWG-issues/papers after approval/rejection by the Committee. + + __begin = __parser_width::__parse(__begin, __end, __parse_ctx); + if (__begin == __end) + return __begin; + + __begin = __parse_type(__begin, static_cast<_Flags&>(*this)); + + if (__begin != __end && *__begin != _CharT('}')) + __throw_format_error("The format-spec should consume the input or end with a '}'"); + + return __begin; + } + + /** Processes the parsed std-format-spec based on the parsed display type. */ + _LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type() { + switch (this->__type) { + case _Flags::_Type::__default: + this->__type = _Flags::_Type::__pointer; + break; + case _Flags::_Type::__pointer: + break; + default: + __throw_format_error("The format-spec type has a type not supported for a pointer argument"); + } + } +}; /** Helper struct returned from @ref __get_string_alignment. */ template diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -279,6 +279,7 @@ #include <__format/formatter_char.h> #include <__format/formatter_floating_point.h> #include <__format/formatter_integer.h> +#include <__format/formatter_pointer.h> #include <__format/formatter_string.h> #include <__format/parser_std_format_spec.h> #include <__variant/monostate.h> diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -475,6 +475,7 @@ module formatter_integer { private header "__format/formatter_integer.h" } module formatter_integral { private header "__format/formatter_integral.h" } module formatter_string { private header "__format/formatter_string.h" } + module formatter_pointer { private header "__format/formatter_pointer.h" } module parser_std_format_spec { private header "__format/parser_std_format_spec.h" } } } diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_pointer.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_pointer.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_pointer.module.verify.cpp @@ -0,0 +1,15 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__format/formatter_pointer.h'}} +#include <__format/formatter_pointer.h> diff --git a/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp b/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp --- a/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp +++ b/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp @@ -346,6 +346,10 @@ // Test pointer types. test(nullptr); + int i = 0; + test(static_cast(&i)); + const int ci = 0; + test(static_cast(&ci)); } void test() { diff --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_pointer.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_pointer.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_pointer.pass.cpp @@ -0,0 +1,254 @@ +//===----------------------------------------------------------------------===// +// 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 + +// + +// Tests the parsing of the format string as specified in [format.string.std]. +// It validates whether the std-format-spec is valid for a pointer type. + +#include +#include +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +# include +#endif + +#include "concepts_precision.h" +#include "test_macros.h" +#include "make_string.h" +#include "test_exception.h" + +#define CSTR(S) MAKE_CSTRING(CharT, S) + +using namespace std::__format_spec; + +template +using Parser = __parser_pointer; + +template +struct Expected { + CharT fill = CharT(' '); + _Flags::_Alignment alignment = _Flags::_Alignment::__right; + _Flags::_Sign sign = _Flags::_Sign::__default; + bool alternate_form = false; + bool zero_padding = false; + uint32_t width = 0; + bool width_as_arg = false; + bool locale_specific_form = false; + _Flags::_Type type = _Flags::_Type::__pointer; +}; + +template +constexpr void test(Expected expected, size_t size, std::basic_string_view fmt) { + // Initialize parser with sufficient arguments to avoid the parsing to fail + // due to insufficient arguments. + std::basic_format_parse_context parse_ctx(fmt, std::__format::__number_max); + auto begin = parse_ctx.begin(); + auto end = parse_ctx.end(); + Parser parser; + auto it = parser.parse(parse_ctx); + + assert(begin == parse_ctx.begin()); + assert(end == parse_ctx.end()); + + assert(begin + size == it); + assert(parser.__fill == expected.fill); + assert(parser.__alignment == expected.alignment); + assert(parser.__sign == expected.sign); + assert(parser.__alternate_form == expected.alternate_form); + assert(parser.__zero_padding == expected.zero_padding); + assert(parser.__width == expected.width); + assert(parser.__width_as_arg == expected.width_as_arg); + assert(parser.__locale_specific_form == expected.locale_specific_form); + assert(parser.__type == expected.type); +} + +template +constexpr void test(Expected expected, size_t size, const CharT* f) { + // The format-spec is valid if completely consumed or terminates at a '}'. + // The valid inputs all end with a '}'. The test is executed twice: + // - first with the terminating '}', + // - second consuming the entire input. + std::basic_string_view fmt{f}; + assert(fmt.back() == CharT('}') && "Pre-condition failure"); + + test(expected, size, fmt); + fmt.remove_suffix(1); + test(expected, size, fmt); +} + +template +constexpr void test() { + Parser parser; + + assert(parser.__fill == CharT(' ')); + assert(parser.__alignment == _Flags::_Alignment::__right); + assert(parser.__sign == _Flags::_Sign::__default); + assert(parser.__alternate_form == false); + assert(parser.__zero_padding == false); + assert(parser.__width == 0); + assert(parser.__width_as_arg == false); + assert(parser.__locale_specific_form == false); + assert(parser.__type == _Flags::_Type::__default); + + test({}, 0, CSTR("}")); + + // *** Align-fill *** + test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}")); + test({.alignment = _Flags::_Alignment::__center}, 1, "^}"); + test({.alignment = _Flags::_Alignment::__right}, 1, ">}"); + + test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 2, CSTR("L<}")); + test({.fill = CharT('#'), .alignment = _Flags::_Alignment::__center}, 2, CSTR("#^}")); + test({.fill = CharT('0'), .alignment = _Flags::_Alignment::__right}, 2, CSTR("0>}")); + + test_exception>("The format-spec fill field contains an invalid character", CSTR("{<")); + test_exception>("The format-spec fill field contains an invalid character", CSTR("}<")); + + // *** Sign *** + test_exception>("The format-spec should consume the input or end with a '}'", CSTR("+")); + test_exception>("The format-spec should consume the input or end with a '}'", CSTR("-")); + test_exception>("The format-spec should consume the input or end with a '}'", CSTR(" ")); + + // *** Alternate form *** + test_exception>("The format-spec should consume the input or end with a '}'", CSTR("#")); + + // *** Zero padding *** + test_exception>("A format-spec width field shouldn't have a leading zero", CSTR("0")); + + // *** Width *** + test({.width = 0, .width_as_arg = false}, 0, CSTR("}")); + test({.width = 1, .width_as_arg = false}, 1, CSTR("1}")); + test({.width = 10, .width_as_arg = false}, 2, CSTR("10}")); + test({.width = 1000, .width_as_arg = false}, 4, CSTR("1000}")); + test({.width = 1000000, .width_as_arg = false}, 7, CSTR("1000000}")); + + test({.width = 0, .width_as_arg = true}, 2, CSTR("{}}")); + test({.width = 0, .width_as_arg = true}, 3, CSTR("{0}}")); + test({.width = 1, .width_as_arg = true}, 3, CSTR("{1}}")); + + test_exception>("A format-spec width field shouldn't have a leading zero", CSTR("00")); + + static_assert(std::__format::__number_max == 2'147'483'647, "Update the assert and the test."); + test({.width = 2'147'483'647, .width_as_arg = false}, 10, CSTR("2147483647}")); + test_exception>("The numeric value of the format-spec is too large", CSTR("2147483648")); + test_exception>("The numeric value of the format-spec is too large", CSTR("5000000000")); + test_exception>("The numeric value of the format-spec is too large", CSTR("10000000000")); + + test_exception>("End of input while parsing format-spec arg-id", CSTR("{")); + test_exception>("A format-spec arg-id should terminate at a '}'", CSTR("{0")); + test_exception>("The arg-id of the format-spec starts with an invalid character", CSTR("{a")); + test_exception>("A format-spec arg-id should terminate at a '}'", CSTR("{1")); + test_exception>("A format-spec arg-id should terminate at a '}'", CSTR("{9")); + test_exception>("A format-spec arg-id should terminate at a '}'", CSTR("{9:")); + test_exception>("A format-spec arg-id should terminate at a '}'", CSTR("{9a")); + static_assert(std::__format::__number_max == 2'147'483'647, "Update the assert and the test."); + // Note the static_assert tests whether the arg-id is valid. + // Therefore the following should be true arg-id < __format::__number_max. + test({.width = 2'147'483'646, .width_as_arg = true}, 12, CSTR("{2147483646}}")); + test_exception>("The numeric value of the format-spec is too large", CSTR("{2147483648}")); + test_exception>("The numeric value of the format-spec is too large", CSTR("{5000000000}")); + test_exception>("The numeric value of the format-spec is too large", CSTR("{10000000000}")); + + // *** Precision *** + test_exception>("The format-spec should consume the input or end with a '}'", CSTR(".")); + test_exception>("The format-spec should consume the input or end with a '}'", CSTR(".1")); + + // *** Locale-specific form *** + test_exception>("The format-spec should consume the input or end with a '}'", CSTR("L")); + + // *** Type *** + { + const char* unsuported_type = "The format-spec type has a type not supported for a pointer argument"; + const char* not_a_type = "The format-spec should consume the input or end with a '}'"; + + test_exception>(unsuported_type, CSTR("A}")); + test_exception>(unsuported_type, CSTR("B}")); + test_exception>(not_a_type, CSTR("C}")); + test_exception>(not_a_type, CSTR("D}")); + test_exception>(unsuported_type, CSTR("E}")); + test_exception>(unsuported_type, CSTR("F}")); + test_exception>(unsuported_type, CSTR("G}")); + test_exception>(not_a_type, CSTR("H}")); + test_exception>(not_a_type, CSTR("I}")); + test_exception>(not_a_type, CSTR("J}")); + test_exception>(not_a_type, CSTR("K}")); + test_exception>("The format-spec should consume the input or end with a '}'", CSTR("L")); + test_exception>(not_a_type, CSTR("M}")); + test_exception>(not_a_type, CSTR("N}")); + test_exception>(not_a_type, CSTR("O}")); + test_exception>(not_a_type, CSTR("P}")); + test_exception>(not_a_type, CSTR("Q}")); + test_exception>(not_a_type, CSTR("R}")); + test_exception>(not_a_type, CSTR("S}")); + test_exception>(not_a_type, CSTR("T}")); + test_exception>(not_a_type, CSTR("U}")); + test_exception>(not_a_type, CSTR("V}")); + test_exception>(not_a_type, CSTR("W}")); + test_exception>(unsuported_type, CSTR("X}")); + test_exception>(not_a_type, CSTR("Y}")); + test_exception>(not_a_type, CSTR("Z}")); + + test_exception>(unsuported_type, CSTR("a}")); + test_exception>(unsuported_type, CSTR("b}")); + test_exception>(unsuported_type, CSTR("c}")); + test_exception>(unsuported_type, CSTR("d}")); + test_exception>(unsuported_type, CSTR("e}")); + test_exception>(unsuported_type, CSTR("f}")); + test_exception>(unsuported_type, CSTR("g}")); + test_exception>(not_a_type, CSTR("h}")); + test_exception>(not_a_type, CSTR("i}")); + test_exception>(not_a_type, CSTR("j}")); + test_exception>(not_a_type, CSTR("k}")); + test_exception>(not_a_type, CSTR("l}")); + test_exception>(not_a_type, CSTR("m}")); + test_exception>(not_a_type, CSTR("n}")); + test_exception>(unsuported_type, CSTR("o}")); + test({.type = _Flags::_Type::__pointer}, 1, CSTR("p}")); + test_exception>(not_a_type, CSTR("q}")); + test_exception>(not_a_type, CSTR("r}")); + test_exception>(unsuported_type, CSTR("s}")); + test_exception>(not_a_type, CSTR("t}")); + test_exception>(not_a_type, CSTR("u}")); + test_exception>(not_a_type, CSTR("v}")); + test_exception>(not_a_type, CSTR("w}")); + test_exception>(unsuported_type, CSTR("x}")); + test_exception>(not_a_type, CSTR("y}")); + test_exception>(not_a_type, CSTR("z}")); + } + + // **** General *** + test_exception>("The format-spec should consume the input or end with a '}'", CSTR("ss")); +} + +constexpr bool test() { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return true; +} + +int main(int, char**) { +#if !defined(_WIN32) && !defined(_AIX) + // Make sure the parsers match the expectations. The layout of the + // subobjects is chosen to minimize the size required. + static_assert(sizeof(Parser) == 2 * sizeof(uint32_t)); +# ifndef TEST_HAS_NO_WIDE_CHARACTERS + static_assert(sizeof(Parser) == (sizeof(wchar_t) <= 2 ? 2 * sizeof(uint32_t) : 3 * sizeof(uint32_t))); +# endif +#endif + + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.pointer.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.pointer.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.pointer.pass.cpp @@ -0,0 +1,107 @@ +//===----------------------------------------------------------------------===// +// 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 + +// + +// [format.formatter.spec]: +// Each header that declares the template `formatter` provides the following +// enabled specializations: +// ... +// For each charT, the pointer type specializations +// - template<> struct formatter; +// - template<> struct formatter; +// - template<> struct formatter; + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" +#include "make_string.h" + +#define STR(S) MAKE_STRING(CharT, S) + +template +void test(StringT expected, StringViewT fmt, PointerT arg) { + using CharT = typename StringT::value_type; + auto parse_ctx = std::basic_format_parse_context(fmt); + std::formatter formatter; + static_assert(std::semiregular); + + auto it = formatter.parse(parse_ctx); + assert(it == fmt.end() - (!fmt.empty() && fmt.back() == '}')); + + StringT result; + auto out = std::back_inserter(result); + using FormatCtxT = std::basic_format_context; + + auto format_ctx = std::__format_context_create(out, std::make_format_args(arg)); + formatter.format(arg, format_ctx); + + if (expected.empty()) { + std::array buffer; + buffer[0] = CharT('0'); + buffer[1] = CharT('x'); + expected.append(buffer.begin(), + std::to_chars(buffer.begin() + 2, buffer.end(), reinterpret_cast(arg), 16).ptr); + } + assert(result == expected); +} + +template +void test_termination_condition(StringT expected, StringT f, PointerT arg) { + // The format-spec is valid if completely consumed or terminates at a '}'. + // The valid inputs all end with a '}'. The test is executed twice: + // - first with the terminating '}', + // - second consuming the entire input. + using CharT = typename StringT::value_type; + std::basic_string_view fmt{f}; + assert(fmt.back() == CharT('}') && "Pre-condition failure"); + + test(expected, fmt, arg); + fmt.remove_suffix(1); + test(expected, fmt, arg); +} + +template +void test_nullptr_t() { + test_termination_condition(STR("0x0"), STR("}"), nullptr); +} + +template +void test_pointer_type() { + test_termination_condition(STR("0x0"), STR("}"), PointerT(0)); + test_termination_condition(STR("0x42"), STR("}"), PointerT(0x42)); + test_termination_condition(STR("0xffff"), STR("}"), PointerT(0xffff)); + test_termination_condition(STR(""), STR("}"), PointerT(-1)); +} + +template +void test_all_pointer_types() { + test_nullptr_t(); + test_pointer_type(); + test_pointer_type(); +} + +int main(int, char**) { + test_all_pointer_types(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_all_pointer_types(); +#endif + + return 0; +} 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 @@ -2471,6 +2471,47 @@ format_test_floating_point(check, check_exception); } +template +void format_test_pointer(TestFunction check, ExceptionTest check_exception) { + // *** align-fill & width *** + check(STR("answer is ' 0x0'"), STR("answer is '{:6}'"), P(nullptr)); + check(STR("answer is ' 0x0'"), STR("answer is '{:>6}'"), P(nullptr)); + check(STR("answer is '0x0 '"), STR("answer is '{:<6}'"), P(nullptr)); + check(STR("answer is ' 0x0 '"), STR("answer is '{:^6}'"), P(nullptr)); + + check(STR("answer is '---0x0'"), STR("answer is '{:->6}'"), P(nullptr)); + check(STR("answer is '0x0---'"), STR("answer is '{:-<6}'"), P(nullptr)); + check(STR("answer is '-0x0--'"), STR("answer is '{:-^6}'"), P(nullptr)); + + // *** Sign *** + check_exception("The format-spec should consume the input or end with a '}'", STR("{:-}"), P(nullptr)); + check_exception("The format-spec should consume the input or end with a '}'", STR("{:+}"), P(nullptr)); + check_exception("The format-spec should consume the input or end with a '}'", STR("{: }"), P(nullptr)); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", STR("{:#}"), P(nullptr)); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", STR("{:0}"), P(nullptr)); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", STR("{:.}"), P(nullptr)); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", STR("{:L}"), P(nullptr)); + + // *** type *** + 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)); +} + +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); +} + template void format_tests(TestFunction check, ExceptionTest check_exception) { // *** Test escaping *** @@ -2598,6 +2639,12 @@ check(STR("hello 42"), STR("hello {}"), static_cast(42)); check(STR("hello 42"), STR("hello {}"), static_cast(42)); format_test_floating_point(check, check_exception); + + // *** Test pointer formater argument *** + check(STR("hello 0x0"), STR("hello {}"), nullptr); + check(STR("hello 0x42"), STR("hello {}"), reinterpret_cast(0x42)); + check(STR("hello 0x42"), STR("hello {}"), reinterpret_cast(0x42)); + format_test_pointer(check, check_exception); } #ifndef TEST_HAS_NO_WIDE_CHARACTERS