[libc++][format] Fixes usage of contiguous ranges.
authorMark de Wever <koraq@xs4all.nl>
Sun, 22 Jan 2023 12:31:27 +0000 (13:31 +0100)
committerMark de Wever <koraq@xs4all.nl>
Tue, 24 Jan 2023 17:08:53 +0000 (18:08 +0100)
The contiguous range made incorrect assumptions for certain input
ranges.

Fixes llvm.org/PR60164

Reviewed By: #libc, ldionne

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

libcxx/include/__format/range_formatter.h
libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.tests.h

index 6188842..9ea61a7 100644 (file)
@@ -29,6 +29,8 @@
 #include <__format/parser_std_format_spec.h>
 #include <__iterator/back_insert_iterator.h>
 #include <__ranges/concepts.h>
+#include <__ranges/data.h>
+#include <__ranges/size.h>
 #include <__type_traits/remove_cvref.h>
 #include <string_view>
 
@@ -165,11 +167,16 @@ struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT range_formatter {
     // When the range is contiguous use a basic_string_view instead to avoid a
     // copy of the underlying data. The basic_string_view formatter
     // specialization is the "basic" string formatter in libc++.
-    if constexpr (ranges::contiguous_range<_Rp>) {
+    if constexpr (ranges::contiguous_range<_Rp> && std::ranges::sized_range<_Rp>) {
       std::formatter<basic_string_view<_CharT>, _CharT> __formatter;
       if (__debug_format)
         __formatter.set_debug_format();
-      return __formatter.format(basic_string_view<_CharT>{__range.data(), __range.size()}, __ctx);
+      return __formatter.format(
+          basic_string_view<_CharT>{
+              ranges::data(__range),
+              ranges::size(__range),
+          },
+          __ctx);
     } else {
       std::formatter<basic_string<_CharT>, _CharT> __formatter;
       if (__debug_format)
index 890f597..a465916 100644 (file)
@@ -12,6 +12,7 @@
 #include <array>
 #include <charconv>
 #include <concepts>
+#include <deque>
 #include <format>
 #include <list>
 #include <ranges>
@@ -1262,6 +1263,66 @@ void test_with_ranges(TestFunction check, ExceptionTest check_exception) {
 }
 
 //
+// Adaptor
+//
+
+template <class CharT>
+class non_contiguous {
+  // A deque iterator is random access, but not contiguous.
+  using adaptee = std::deque<CharT>;
+
+public:
+  using iterator = typename adaptee::iterator;
+  using pointer  = typename adaptee::pointer;
+
+  iterator begin() { return data_.begin(); }
+  iterator end() { return data_.end(); }
+
+  explicit non_contiguous(adaptee&& data) : data_(std::move(data)) {}
+
+private:
+  adaptee data_;
+};
+
+template <class CharT>
+class contiguous {
+  // A vector iterator is contiguous.
+  using adaptee = std::vector<CharT>;
+
+public:
+  using iterator = typename adaptee::iterator;
+  using pointer  = typename adaptee::pointer;
+
+  iterator begin() { return data_.begin(); }
+  iterator end() { return data_.end(); }
+
+  explicit contiguous(adaptee&& data) : data_(std::move(data)) {}
+
+private:
+  adaptee data_;
+};
+
+// This tests two different implementations in libc++. A basic_string_view
+// formatter if the range is contiguous, a basic_string otherwise.
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_adaptor(TestFunction check, ExceptionTest check_exception) {
+  static_assert(std::format_kind<non_contiguous<CharT>> == std::range_format::sequence);
+  static_assert(std::ranges::sized_range<non_contiguous<CharT>>);
+  static_assert(!std::ranges::contiguous_range<non_contiguous<CharT>>);
+  test_char_string<CharT>(
+      check,
+      check_exception,
+      non_contiguous<CharT>{std::deque{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}});
+
+  static_assert(std::format_kind<contiguous<CharT>> == std::range_format::sequence);
+  static_assert(std::ranges::sized_range<contiguous<CharT>>);
+  static_assert(std::ranges::contiguous_range<contiguous<CharT>>);
+  test_char_string<CharT>(check,
+                          check_exception,
+                          contiguous<CharT>{std::vector{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}});
+}
+
+//
 // Driver
 //
 
@@ -1285,6 +1346,8 @@ void format_tests(TestFunction check, ExceptionTest check_exception) {
   test_tuple_int_int_int<CharT>(check, check_exception);
 
   test_with_ranges<CharT>(check, check_exception);
+
+  test_adaptor<CharT>(check, check_exception);
 }
 
 #endif // TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FORMATTER_FORMAT_FUNCTIONS_TESTS_H