[libc++][format] Adds formatter for tuple and pair

Implements parts of
- P2286R8 Formatting Ranges

Reviewed By: ldionne, #libc

Differential Revision: https://reviews.llvm.org/D136775

NOKEYCHECK=True
GitOrigin-RevId: eb6e13cb32805ee12d19aaa5823f3e4216a35653
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index ea1e23a..59087b4 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -313,6 +313,7 @@
   __format/formatter_output.h
   __format/formatter_pointer.h
   __format/formatter_string.h
+  __format/formatter_tuple.h
   __format/parser_std_format_spec.h
   __format/range_default_formatter.h
   __format/unicode.h
diff --git a/include/__chrono/statically_widen.h b/include/__chrono/statically_widen.h
index dd12c3f..360b6c2 100644
--- a/include/__chrono/statically_widen.h
+++ b/include/__chrono/statically_widen.h
@@ -26,26 +26,26 @@
 
 #  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
 template <__fmt_char_type _CharT>
-consteval const _CharT* __statically_widen(const char* __str, const wchar_t* __wstr) {
+_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* __statically_widen(const char* __str, const wchar_t* __wstr) {
   if constexpr (same_as<_CharT, char>)
     return __str;
   else
     return __wstr;
 }
 #    define _LIBCPP_STATICALLY_WIDEN(_CharT, __str) ::std::__statically_widen<_CharT>(__str, L##__str)
-#  else // _LIBCPP_HAS_NO_WIDE_CHARACTERS
+#  else  // _LIBCPP_HAS_NO_WIDE_CHARACTERS
 
 // Without this indirection the unit test test/libcxx/modules_include.sh.cpp
 // fails for the CI build "No wide characters". This seems like a bug.
 // TODO FMT investigate why this is needed.
 template <__fmt_char_type _CharT>
-consteval const _CharT* __statically_widen(const char* __str) {
+_LIBCPP_HIDE_FROM_ABI constexpr const _CharT* __statically_widen(const char* __str) {
   return __str;
 }
 #    define _LIBCPP_STATICALLY_WIDEN(_CharT, __str) ::std::__statically_widen<_CharT>(__str)
 #  endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
 
-#endif //if _LIBCPP_STD_VER > 17
+#endif   //_LIBCPP_STD_VER > 17
 
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/include/__format/formatter.h b/include/__format/formatter.h
index 154f8a5..900a09a 100644
--- a/include/__format/formatter.h
+++ b/include/__format/formatter.h
@@ -38,7 +38,16 @@
   formatter& operator=(const formatter&) = delete;
 };
 
-#endif //_LIBCPP_STD_VER > 17
+#  if _LIBCPP_STD_VER > 20
+
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr void __set_debug_format(_Tp& __formatter) {
+  if constexpr (requires { __formatter.set_debug_format(); })
+    __formatter.set_debug_format();
+}
+
+#  endif // _LIBCPP_STD_VER > 20
+#endif   // _LIBCPP_STD_VER > 17
 
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/include/__format/formatter_tuple.h b/include/__format/formatter_tuple.h
new file mode 100644
index 0000000..82f5ada
--- /dev/null
+++ b/include/__format/formatter_tuple.h
@@ -0,0 +1,178 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___FORMAT_FORMATTER_TUPLE_H
+#define _LIBCPP___FORMAT_FORMATTER_TUPLE_H
+
+#include <__algorithm/ranges_copy.h>
+#include <__availability>
+#include <__chrono/statically_widen.h>
+#include <__config>
+#include <__format/concepts.h>
+#include <__format/format_args.h>
+#include <__format/format_context.h>
+#include <__format/format_error.h>
+#include <__format/format_parse_context.h>
+#include <__format/formatter.h>
+#include <__format/formatter_output.h>
+#include <__format/parser_std_format_spec.h>
+#include <__iterator/back_insert_iterator.h>
+#include <__type_traits/remove_cvref.h>
+#include <__utility/integer_sequence.h>
+#include <__utility/pair.h>
+#include <string_view>
+#include <tuple>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER > 20
+
+template <__fmt_char_type _CharT, class _Tuple, formattable<_CharT>... _Args>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT __formatter_tuple {
+  _LIBCPP_HIDE_FROM_ABI constexpr void set_separator(basic_string_view<_CharT> __separator) {
+    __separator_ = __separator;
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr void
+  set_brackets(basic_string_view<_CharT> __opening_bracket, basic_string_view<_CharT> __closing_bracket) {
+    __opening_bracket_ = __opening_bracket;
+    __closing_bracket_ = __closing_bracket;
+  }
+
+  template <class _ParseContext>
+  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __parse_ctx) {
+    const _CharT* __begin = __parser_.__parse(__parse_ctx, __format_spec::__fields_tuple);
+
+    // [format.tuple]/7
+    //   ... For each element e in underlying_, if e.set_debug_format()
+    //   is a valid expression, calls e.set_debug_format().
+    // TODO FMT this can be removed when P2733 is accepted.
+    std::__for_each_index_sequence(make_index_sequence<sizeof...(_Args)>(), [&]<size_t _Index> {
+      std::__set_debug_format(std::get<_Index>(__underlying_));
+    });
+
+    const _CharT* __end = __parse_ctx.end();
+    if (__begin == __end)
+      return __begin;
+
+    if (*__begin == _CharT('m')) {
+      if constexpr (sizeof...(_Args) == 2) {
+        set_separator(_LIBCPP_STATICALLY_WIDEN(_CharT, ": "));
+        set_brackets({}, {});
+        ++__begin;
+      } else
+        std::__throw_format_error("The format specifier m requires a pair or a two-element tuple");
+    } else if (*__begin == _CharT('n')) {
+      set_brackets({}, {});
+      ++__begin;
+    }
+
+    if (__begin != __end && *__begin != _CharT('}'))
+      std::__throw_format_error("The format-spec should consume the input or end with a '}'");
+
+    return __begin;
+  }
+
+  template <class _FormatContext>
+  typename _FormatContext::iterator _LIBCPP_HIDE_FROM_ABI
+  format(conditional_t<(formattable<const _Args, _CharT> && ...), const _Tuple&, _Tuple&> __tuple,
+         _FormatContext& __ctx) const {
+    __format_spec::__parsed_specifications<_CharT> __specs = __parser_.__get_parsed_std_specifications(__ctx);
+
+    if (!__specs.__has_width())
+      return __format_tuple(__tuple, __ctx);
+
+    basic_string<_CharT> __str;
+
+    // Since the output is written to a different iterator a new context is
+    // created. Since the underlying formatter uses the default formatting it
+    // doesn't need a locale or the formatting arguments. So creating a new
+    // context works.
+    //
+    // This solution works for this formatter, but it will not work for the
+    // range_formatter. In that patch a generic solution is work in progress.
+    // Once that is finished it can be used here. (The range_formatter will use
+    // these features so it's easier to add it there and then port it.)
+    //
+    // TODO FMT Use formatting wrapping used in the range_formatter.
+    basic_format_context __c = std::__format_context_create(
+        back_insert_iterator{__str},
+        basic_format_args<basic_format_context<back_insert_iterator<basic_string<_CharT>>, _CharT>>{});
+
+    __format_tuple(__tuple, __c);
+
+    return __formatter::__write_string_no_precision(basic_string_view{__str}, __ctx.out(), __specs);
+  }
+
+  template <class _FormatContext>
+  _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator __format_tuple(auto&& __tuple, _FormatContext& __ctx) const {
+    __ctx.advance_to(std::ranges::copy(__opening_bracket_, __ctx.out()).out);
+
+    std::__for_each_index_sequence(make_index_sequence<sizeof...(_Args)>(), [&]<size_t _Index> {
+      if constexpr (_Index)
+        __ctx.advance_to(std::ranges::copy(__separator_, __ctx.out()).out);
+
+        // During review Victor suggested to make the exposition only
+        // __underlying_ member a local variable. Currently the Standard
+        // requires nested debug-enabled formatter specializations not to
+        // output escaped output. P2733 fixes that bug, once accepted the
+        // code below can be used.
+        // (Note when a paper allows parsing a tuple-underlying-spec the
+        // exposition only member needs to be a class member. Earlier
+        // revisions of P2286 proposed that, but this was not pursued,
+        // due to time constrains and complexity of the matter.)
+        // TODO FMT This can be updated after P2733 is accepted.
+#  if 0
+      // P2286 uses an exposition only member in the formatter
+      //   tuple<formatter<remove_cvref_t<_Args>, _CharT>...> __underlying_;
+      // This was used in earlier versions of the paper since
+      // __underlying_.parse(...) was called. This is no longer the case
+      // so we can reduce the scope of the formatter.
+      //
+      // It does require the underlying's parse effect to be moved here too.
+      using _Arg = tuple_element<_Index, decltype(__tuple)>;
+      formatter<remove_cvref_t<_Args>, _CharT> __underlying;
+
+      // [format.tuple]/7
+      //   ... For each element e in underlying_, if e.set_debug_format()
+      //   is a valid expression, calls e.set_debug_format().
+      std::__set_debug_format(__underlying);
+#  else
+      __ctx.advance_to(std::get<_Index>(__underlying_).format(std::get<_Index>(__tuple), __ctx));
+#  endif
+    });
+
+    return std::ranges::copy(__closing_bracket_, __ctx.out()).out;
+  }
+
+  __format_spec::__parser<_CharT> __parser_{.__alignment_ = __format_spec::__alignment::__left};
+
+private:
+  tuple<formatter<remove_cvref_t<_Args>, _CharT>...> __underlying_;
+  basic_string_view<_CharT> __separator_       = _LIBCPP_STATICALLY_WIDEN(_CharT, ", ");
+  basic_string_view<_CharT> __opening_bracket_ = _LIBCPP_STATICALLY_WIDEN(_CharT, "(");
+  basic_string_view<_CharT> __closing_bracket_ = _LIBCPP_STATICALLY_WIDEN(_CharT, ")");
+};
+
+template <__fmt_char_type _CharT, formattable<_CharT>... _Args>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<pair<_Args...>, _CharT>
+    : public __formatter_tuple<_CharT, pair<_Args...>, _Args...> {};
+
+template <__fmt_char_type _CharT, formattable<_CharT>... _Args>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<tuple<_Args...>, _CharT>
+    : public __formatter_tuple<_CharT, tuple<_Args...>, _Args...> {};
+
+#endif //_LIBCPP_STD_VER > 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___FORMAT_FORMATTER_TUPLE_H
diff --git a/include/__format/parser_std_format_spec.h b/include/__format/parser_std_format_spec.h
index 759ace2..26d7eb4 100644
--- a/include/__format/parser_std_format_spec.h
+++ b/include/__format/parser_std_format_spec.h
@@ -101,6 +101,7 @@
 ///
 /// They default to false so when a new field is added it needs to be opted in
 /// explicitly.
+// TODO FMT Use an ABI tag for this struct.
 struct __fields {
   uint8_t __sign_ : 1 {false};
   uint8_t __alternate_form_ : 1 {false};
@@ -108,6 +109,13 @@
   uint8_t __precision_ : 1 {false};
   uint8_t __locale_specific_form_ : 1 {false};
   uint8_t __type_ : 1 {false};
+  // Determines the valid values for fill.
+  //
+  // Originally the fill could be any character except { and }. Range-based
+  // formatters use the colon to mark the beginning of the
+  // underlying-format-spec. To avoid parsing ambiguities these formatter
+  // specializations prohibit the use of the colon as a fill character.
+  uint8_t __allow_colon_in_fill_ : 1 {false};
 };
 
 // By not placing this constant in the formatter class it's not duplicated for
@@ -128,6 +136,10 @@
 inline constexpr __fields __fields_string{.__precision_ = true, .__type_ = true};
 inline constexpr __fields __fields_pointer{.__type_ = true};
 
+#  if _LIBCPP_STD_VER > 20
+inline constexpr __fields __fields_tuple{.__type_ = false, .__allow_colon_in_fill_ = true};
+#  endif
+
 enum class _LIBCPP_ENUM_VIS __alignment : uint8_t {
   /// No alignment is set in the format string.
   __default,
@@ -256,7 +268,7 @@
     if (__begin == __end)
       return __begin;
 
-    if (__parse_fill_align(__begin, __end) && __begin == __end)
+    if (__parse_fill_align(__begin, __end, __fields.__allow_colon_in_fill_) && __begin == __end)
       return __begin;
 
     if (__fields.__sign_ && __parse_sign(__begin) && __begin == __end)
@@ -364,12 +376,16 @@
     return false;
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr bool __parse_fill_align(const _CharT*& __begin, const _CharT* __end) {
+  // range-fill and tuple-fill are identical
+  _LIBCPP_HIDE_FROM_ABI constexpr bool
+  __parse_fill_align(const _CharT*& __begin, const _CharT* __end, bool __use_range_fill) {
     _LIBCPP_ASSERT(__begin != __end, "when called with an empty input the function will cause "
                                      "undefined behavior by evaluating data not in the input");
     if (__begin + 1 != __end) {
       if (__parse_alignment(*(__begin + 1))) {
-        if (*__begin == _CharT('{') || *__begin == _CharT('}'))
+        if (__use_range_fill && (*__begin == _CharT('{') || *__begin == _CharT('}') || *__begin == _CharT(':')))
+          std::__throw_format_error("The format-spec range-fill field contains an invalid character");
+        else if (*__begin == _CharT('{') || *__begin == _CharT('}'))
           std::__throw_format_error("The format-spec fill field contains an invalid character");
 
         __fill_ = *__begin;
diff --git a/include/__utility/integer_sequence.h b/include/__utility/integer_sequence.h
index 87c76e9..9ba9d94 100644
--- a/include/__utility/integer_sequence.h
+++ b/include/__utility/integer_sequence.h
@@ -137,6 +137,14 @@
 template<class... _Tp>
     using index_sequence_for = make_index_sequence<sizeof...(_Tp)>;
 
+#  if _LIBCPP_STD_VER > 17
+// Executes __func for every element in an index_sequence.
+template <size_t... _Index, class _Function>
+_LIBCPP_HIDE_FROM_ABI constexpr void __for_each_index_sequence(index_sequence<_Index...>, _Function __func) {
+    (__func.template operator()<_Index>(), ...);
+}
+#  endif // _LIBCPP_STD_VER > 17
+
 #endif // _LIBCPP_STD_VER > 11
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/include/format b/include/format
index e5f8976..900f27c 100644
--- a/include/format
+++ b/include/format
@@ -191,6 +191,7 @@
 #include <__format/formatter_integer.h>
 #include <__format/formatter_pointer.h>
 #include <__format/formatter_string.h>
+#include <__format/formatter_tuple.h>
 #include <__format/parser_std_format_spec.h>
 #include <__format/range_default_formatter.h>
 #include <__format/unicode.h>
diff --git a/include/module.modulemap.in b/include/module.modulemap.in
index c0473a4..a19a3c4 100644
--- a/include/module.modulemap.in
+++ b/include/module.modulemap.in
@@ -856,6 +856,7 @@
       module formatter_output                { private header "__format/formatter_output.h" }
       module formatter_pointer               { private header "__format/formatter_pointer.h" }
       module formatter_string                { private header "__format/formatter_string.h" }
+      module formatter_tuple                 { private header "__format/formatter_tuple.h" }
       module parser_std_format_spec          { private header "__format/parser_std_format_spec.h" }
       module range_default_formatter         { private header "__format/range_default_formatter.h" }
       module unicode                         { private header "__format/unicode.h" }