Skip to content

Commit ff3ca78

Browse files
ObeliskGatehenryiiipre-commit-ci[bot]
committed
fix: <ranges> support for py::tuple and py::list (#5314)
* feat: add `<ranges>` support for `py::tuple` and `py::list` * fix: format the code * fix: disable `ranges` in clang < 16 * refactor: move `<ranges>` test macro to `test_pytypes.h` * refactor: seperate `ranges` test into 3 funcs * style: compress the if statement * style: pre-commit fixes * style: better formatting --------- Co-authored-by: Henry Schreiner <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent b0050f3 commit ff3ca78

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

include/pybind11/pytypes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,7 @@ class sequence_fast_readonly {
12591259
using pointer = arrow_proxy<const handle>;
12601260

12611261
sequence_fast_readonly(handle obj, ssize_t n) : ptr(PySequence_Fast_ITEMS(obj.ptr()) + n) {}
1262+
sequence_fast_readonly() = default;
12621263

12631264
// NOLINTNEXTLINE(readability-const-return-type) // PR #3263
12641265
reference dereference() const { return *ptr; }
@@ -1281,6 +1282,7 @@ class sequence_slow_readwrite {
12811282
using pointer = arrow_proxy<const sequence_accessor>;
12821283

12831284
sequence_slow_readwrite(handle obj, ssize_t index) : obj(obj), index(index) {}
1285+
sequence_slow_readwrite() = default;
12841286

12851287
reference dereference() const { return {obj, static_cast<size_t>(index)}; }
12861288
void increment() { ++index; }

tests/test_pytypes.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313

1414
#include <utility>
1515

16+
//__has_include has been part of C++17, no need to check it
17+
#if defined(PYBIND11_CPP20) && __has_include(<ranges>)
18+
# if !defined(PYBIND11_COMPILER_CLANG) || __clang_major__ >= 16 // llvm/llvm-project#52696
19+
# define PYBIND11_TEST_PYTYPES_HAS_RANGES
20+
# include <ranges>
21+
# endif
22+
#endif
23+
1624
namespace external {
1725
namespace detail {
1826
bool check(PyObject *o) { return PyFloat_Check(o) != 0; }
@@ -923,4 +931,59 @@ TEST_SUBMODULE(pytypes, m) {
923931
#else
924932
m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = false;
925933
#endif
934+
935+
#if defined(PYBIND11_TEST_PYTYPES_HAS_RANGES)
936+
937+
// test_tuple_ranges
938+
m.def("tuple_iterator_default_initialization", []() {
939+
using TupleIterator = decltype(std::declval<py::tuple>().begin());
940+
static_assert(std::random_access_iterator<TupleIterator>);
941+
return TupleIterator{} == TupleIterator{};
942+
});
943+
944+
m.def("transform_tuple_plus_one", [](py::tuple &tpl) {
945+
py::list ret{};
946+
for (auto it : tpl | std::views::transform([](auto &o) { return py::cast<int>(o) + 1; })) {
947+
ret.append(py::int_(it));
948+
}
949+
return ret;
950+
});
951+
952+
// test_list_ranges
953+
m.def("list_iterator_default_initialization", []() {
954+
using ListIterator = decltype(std::declval<py::list>().begin());
955+
static_assert(std::random_access_iterator<ListIterator>);
956+
return ListIterator{} == ListIterator{};
957+
});
958+
959+
m.def("transform_list_plus_one", [](py::list &lst) {
960+
py::list ret{};
961+
for (auto it : lst | std::views::transform([](auto &o) { return py::cast<int>(o) + 1; })) {
962+
ret.append(py::int_(it));
963+
}
964+
return ret;
965+
});
966+
967+
// test_dict_ranges
968+
m.def("dict_iterator_default_initialization", []() {
969+
using DictIterator = decltype(std::declval<py::dict>().begin());
970+
static_assert(std::forward_iterator<DictIterator>);
971+
return DictIterator{} == DictIterator{};
972+
});
973+
974+
m.def("transform_dict_plus_one", [](py::dict &dct) {
975+
py::list ret{};
976+
for (auto it : dct | std::views::transform([](auto &o) {
977+
return std::pair{py::cast<int>(o.first) + 1,
978+
py::cast<int>(o.second) + 1};
979+
})) {
980+
ret.append(py::make_tuple(py::int_(it.first), py::int_(it.second)));
981+
}
982+
return ret;
983+
});
984+
985+
m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = true;
986+
#else
987+
m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false;
988+
#endif
926989
}

tests/test_pytypes.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,3 +1048,45 @@ def test_typevar(doc):
10481048
assert doc(m.annotate_listT_to_T) == "annotate_listT_to_T(arg0: list[T]) -> T"
10491049

10501050
assert doc(m.annotate_object_to_T) == "annotate_object_to_T(arg0: object) -> T"
1051+
1052+
1053+
@pytest.mark.skipif(
1054+
not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES,
1055+
reason="<ranges> not available.",
1056+
)
1057+
@pytest.mark.parametrize(
1058+
("tested_tuple", "expected"),
1059+
[((1,), [2]), ((3, 4), [4, 5]), ((7, 8, 9), [8, 9, 10])],
1060+
)
1061+
def test_tuple_ranges(tested_tuple, expected):
1062+
assert m.tuple_iterator_default_initialization()
1063+
assert m.transform_tuple_plus_one(tested_tuple) == expected
1064+
1065+
1066+
@pytest.mark.skipif(
1067+
not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES,
1068+
reason="<ranges> not available.",
1069+
)
1070+
@pytest.mark.parametrize(
1071+
("tested_list", "expected"), [([1], [2]), ([3, 4], [4, 5]), ([7, 8, 9], [8, 9, 10])]
1072+
)
1073+
def test_list_ranges(tested_list, expected):
1074+
assert m.list_iterator_default_initialization()
1075+
assert m.transform_list_plus_one(tested_list) == expected
1076+
1077+
1078+
@pytest.mark.skipif(
1079+
not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES,
1080+
reason="<ranges> not available.",
1081+
)
1082+
@pytest.mark.parametrize(
1083+
("tested_dict", "expected"),
1084+
[
1085+
({1: 2}, [(2, 3)]),
1086+
({3: 4, 5: 6}, [(4, 5), (6, 7)]),
1087+
({7: 8, 9: 10, 11: 12}, [(8, 9), (10, 11), (12, 13)]),
1088+
],
1089+
)
1090+
def test_dict_ranges(tested_dict, expected):
1091+
assert m.dict_iterator_default_initialization()
1092+
assert m.transform_dict_plus_one(tested_dict) == expected

0 commit comments

Comments
 (0)