Skip to content

Commit

Permalink
[libc++][format] Adds formatter pointer.
Browse files Browse the repository at this point in the history
This implements the last required formatter specialization.

Completes:
- LWG 3251 Are std::format alignment specifiers applied to string arguments?
- LWG 3340 Formatting functions should throw on argument/format string mismatch in §[format.functions]
- LWG 3540 §[format.arg] There should be no const in basic_format_arg(const T* p)

Implements parts of:
- P0645 Text Formatting

Depends on D114001

Reviewed By: ldionne, vitaut, #libc

Differential Revision: https://reviews.llvm.org/D115988
  • Loading branch information
mordante committed Jan 24, 2022
1 parent db2944e commit 787ccd3
Show file tree
Hide file tree
Showing 13 changed files with 629 additions and 5 deletions.
4 changes: 2 additions & 2 deletions libcxx/docs/Status/Cxx20Issues.csv
Expand Up @@ -207,7 +207,7 @@
"`3247 <https://wg21.link/LWG3247>`__","``ranges::iter_move``\ should perform ADL-only lookup of ``iter_move``\ ","Prague","","","|ranges|"
"`3248 <https://wg21.link/LWG3248>`__","``std::format``\ ``#b``\ , ``#B``\ , ``#o``\ , ``#x``\ , and ``#X``\ presentation types misformat negative numbers","Prague","|Complete|","14.0","|format|"
"`3250 <https://wg21.link/LWG3250>`__","``std::format``\ : ``#``\ (alternate form) for NaN and inf","Prague","|Complete|","14.0","|format|"
"`3251 <https://wg21.link/LWG3251>`__","Are ``std::format``\ alignment specifiers applied to string arguments?","Prague","","","|format|"
"`3251 <https://wg21.link/LWG3251>`__","Are ``std::format``\ alignment specifiers applied to string arguments?","Prague","|Complete|","14.0","|format|"
"`3252 <https://wg21.link/LWG3252>`__","Parse locale's aware modifiers for commands are not consistent with POSIX spec","Prague","","","|chrono|"
"`3254 <https://wg21.link/LWG3254>`__","Strike ``stop_token``\ 's ``operator!=``\ ","Prague","",""
"`3255 <https://wg21.link/LWG3255>`__","``span``\ 's ``array``\ constructor is too strict","Prague","|Complete|",""
Expand Down Expand Up @@ -256,7 +256,7 @@
"`3334 <https://wg21.link/LWG3334>`__","``basic_osyncstream``\ move assignment and destruction calls ``basic_syncbuf::emit()``\ twice","Prague","",""
"`3335 <https://wg21.link/LWG3335>`__","Resolve C++20 NB comments US 273 and GB 274","Prague","","","|ranges|"
"`3338 <https://wg21.link/LWG3338>`__","Rename ``default_constructible``\ to ``default_initializable``\ ","Prague","|Complete|","13.0"
"`3340 <https://wg21.link/LWG3340>`__","Formatting functions should throw on argument/format string mismatch in |sect|\ [format.functions]","Prague","","","|format|"
"`3340 <https://wg21.link/LWG3340>`__","Formatting functions should throw on argument/format string mismatch in |sect|\ [format.functions]","Prague","|Complete|","14.0","|format|"
"`3346 <https://wg21.link/LWG3346>`__","``pair``\ and ``tuple``\ copy and move constructor have backwards specification","Prague","",""
"`3347 <https://wg21.link/LWG3347>`__","``std::pair<T, U>``\ now requires ``T``\ and ``U``\ to be less-than-comparable","Prague","",""
"`3348 <https://wg21.link/LWG3348>`__","``__cpp_lib_unwrap_ref``\ in wrong header","Prague","|Complete|","12.0"
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2bIssues.csv
Expand Up @@ -84,7 +84,7 @@
`3533 <https://wg21.link/LWG3533>`__,"Make ``base() const &`` consistent across iterator wrappers that supports ``input_iterators``","June 2021","","","|ranges|"
`3536 <https://wg21.link/LWG3536>`__,"Should ``chrono::from_stream()`` assign zero to duration for failure?","June 2021","","","|chrono|"
`3539 <https://wg21.link/LWG3539>`__,"``format_to`` must not copy models of ``output_iterator<const charT&>``","June 2021","","","|format|"
`3540 <https://wg21.link/LWG3540>`__,"§[format.arg] There should be no const in ``basic_format_arg(const T* p)``","June 2021","","","|format|"
`3540 <https://wg21.link/LWG3540>`__,"§[format.arg] There should be no const in ``basic_format_arg(const T* p)``","June 2021","|Complete|","14.0","|format|"
`3541 <https://wg21.link/LWG3541>`__,"``indirectly_readable_traits`` should be SFINAE-friendly for all types","June 2021","|Complete|","14.0","|ranges|"
`3542 <https://wg21.link/LWG3542>`__,"``basic_format_arg`` mishandles ``basic_string_view`` with custom traits","June 2021","|Complete|","14.0","|format|"
`3543 <https://wg21.link/LWG3543>`__,"Definition of when ``counted_iterators`` refer to the same sequence isn't quite right","June 2021","","","|ranges|"
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Expand Up @@ -181,6 +181,7 @@ set(files
__format/formatter_floating_point.h
__format/formatter_integer.h
__format/formatter_integral.h
__format/formatter_pointer.h
__format/formatter_string.h
__format/parser_std_format_spec.h
__functional/binary_function.h
Expand Down
4 changes: 3 additions & 1 deletion libcxx/include/__format/format_arg.h
Expand Up @@ -245,7 +245,9 @@ class _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT basic_format_arg {
explicit basic_format_arg(nullptr_t) noexcept
: __ptr(nullptr), __type_(__format::__arg_t::__ptr) {}

// TODO FMT Implement the _Tp* constructor.
template <class _Tp>
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)
Expand Down
91 changes: 91 additions & 0 deletions 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 <cstdint>

#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<uintptr_t>(__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<nullptr_t, charT>;
// - template<> struct formatter<void*, charT>;
// - template<> struct formatter<const void*, charT>;
template <__formatter::__char_type _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<nullptr_t, _CharT>
: public __format_spec::__formatter_pointer<_CharT> {};
template <__formatter::__char_type _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<void*, _CharT>
: public __format_spec::__formatter_pointer<_CharT> {};
template <__formatter::__char_type _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<const void*, _CharT>
: 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
103 changes: 102 additions & 1 deletion libcxx/include/__format/parser_std_format_spec.h
Expand Up @@ -826,7 +826,108 @@ class _LIBCPP_TEMPLATE_VIS __parser_floating_point
}
};

// 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 _CharT>
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 <class _CharT>
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/format
Expand Up @@ -279,6 +279,7 @@ namespace std {
#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>
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/module.modulemap
Expand Up @@ -506,6 +506,7 @@ module std [system] {
module formatter_floating_point { private header "__format/formatter_floating_point.h" }
module formatter_integer { private header "__format/formatter_integer.h" }
module formatter_integral { private header "__format/formatter_integral.h" }
module formatter_pointer { private header "__format/formatter_pointer.h" }
module formatter_string { private header "__format/formatter_string.h" }
module parser_std_format_spec { private header "__format/parser_std_format_spec.h" }
}
Expand Down
@@ -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>
Expand Up @@ -346,6 +346,10 @@ void test() {
// Test pointer types.

test<Context, const void*>(nullptr);
int i = 0;
test<Context, const void*>(static_cast<void*>(&i));
const int ci = 0;
test<Context, const void*>(static_cast<const void*>(&ci));
}

void test() {
Expand Down

0 comments on commit 787ccd3

Please sign in to comment.