Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: <ranges> support for py::tuple and py::list #5314

Merged
merged 8 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,18 @@ PYBIND11_WARNING_DISABLE_MSVC(4505)
# endif
#endif

#if defined(PYBIND11_CPP20)
# if __has_include(<ranges>) // __has_include has been part of C++17, no need to check it
# if !defined(PYBIND11_COMPILER_CLANG)
# define PYBIND11_HAS_RANGES
ObeliskGate marked this conversation as resolved.
Show resolved Hide resolved
# else
# if __clang_major__ >= 16 // llvm/llvm-project#52696
# define PYBIND11_HAS_RANGES
# endif
# endif
# endif
#endif

#include <Python.h>
#if PY_VERSION_HEX < 0x03080000
# error "PYTHON < 3.8 IS UNSUPPORTED. pybind11 v2.13 was the last to support Python 3.7."
Expand Down
2 changes: 2 additions & 0 deletions include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,7 @@ class sequence_fast_readonly {
using pointer = arrow_proxy<const handle>;

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

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

sequence_slow_readwrite(handle obj, ssize_t index) : obj(obj), index(index) {}
sequence_slow_readwrite() = default;

reference dereference() const { return {obj, static_cast<size_t>(index)}; }
void increment() { ++index; }
Expand Down
40 changes: 40 additions & 0 deletions tests/test_pytypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
#include "pybind11_tests.h"

#include <utility>
#if defined(PYBIND11_HAS_RANGES)
ObeliskGate marked this conversation as resolved.
Show resolved Hide resolved
# include <ranges>
#endif

namespace external {
namespace detail {
Expand Down Expand Up @@ -923,4 +926,41 @@ TEST_SUBMODULE(pytypes, m) {
#else
m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = false;
#endif

#if defined(PYBIND11_HAS_RANGES) // test_ranges
m.attr("defined_PYBIND11_HAS_RANGES") = true;
ObeliskGate marked this conversation as resolved.
Show resolved Hide resolved
m.def("iterator_default_initialization", []() {
using TupleIterator = decltype(py::tuple{}.begin());
using ListIterator = decltype(py::list{}.begin());
using DictIterator = decltype(py::dict{}.begin());
return py::bool_(
TupleIterator{} == TupleIterator{} && ListIterator{} == ListIterator{}
ObeliskGate marked this conversation as resolved.
Show resolved Hide resolved
&& DictIterator{}
== DictIterator{}); // value initialization result must compared as true
});

m.def("transform_tuple_plus_one", [](py::tuple &tpl) {
static_assert(std::ranges::random_access_range<py::tuple>);
for (auto it : tpl | std::views::transform([](auto &o) { return py::cast<int>(o) + 1; })) {
py::print(it);
ObeliskGate marked this conversation as resolved.
Show resolved Hide resolved
}
});
m.def("transform_list_plus_one", [](py::list &lst) {
static_assert(std::ranges::random_access_range<py::list>);
for (auto it : lst | std::views::transform([](auto &o) { return py::cast<int>(o) + 1; })) {
py::print(it);
}
});
m.def("transform_dict_plus_one", [](py::dict &dct) {
static_assert(std::ranges::forward_range<py::dict>);
for (auto it : dct | std::views::transform([](auto &o) {
return std::pair{py::cast<int>(o.first) + 1,
py::cast<int>(o.second) + 1};
})) {
py::print("{} : {}"_s.format(it.first, it.second));
}
});
#else
m.attr("defined_PYBIND11_HAS_RANGES") = false;
#endif
}
43 changes: 43 additions & 0 deletions tests/test_pytypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,3 +1048,46 @@ def test_typevar(doc):
assert doc(m.annotate_listT_to_T) == "annotate_listT_to_T(arg0: list[T]) -> T"

assert doc(m.annotate_object_to_T) == "annotate_object_to_T(arg0: object) -> T"


@pytest.mark.skipif(
not m.defined_PYBIND11_HAS_RANGES, reason="C++20 <ranges> not available."
ObeliskGate marked this conversation as resolved.
Show resolved Hide resolved
)
def test_ranges(capture):
ObeliskGate marked this conversation as resolved.
Show resolved Hide resolved
assert m.iterator_default_initialization
ObeliskGate marked this conversation as resolved.
Show resolved Hide resolved

with capture:
m.transform_tuple_plus_one((1, 2, 3))

assert (
capture.unordered
== """
2
3
4
"""
)

with capture:
m.transform_list_plus_one([1, 2, 3])

assert (
capture.unordered
== """
2
3
4
"""
)

with capture:
m.transform_dict_plus_one({1: 2, 3: 4, 5: 6})

assert (
capture.unordered
== """
2 : 3
4 : 5
6 : 7
"""
)