From ccb907ffc31cd26281f85642742cc670fab422ec Mon Sep 17 00:00:00 2001 From: ObeliskGate Date: Fri, 16 Aug 2024 12:02:43 +0800 Subject: [PATCH 1/8] feat: add `` support for `py::tuple` and `py::list` --- include/pybind11/detail/common.h | 6 +++++ include/pybind11/pytypes.h | 2 ++ tests/test_pytypes.cpp | 40 ++++++++++++++++++++++++++++++ tests/test_pytypes.py | 42 ++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 643fd33d7e..61304501ac 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -271,6 +271,12 @@ PYBIND11_WARNING_DISABLE_MSVC(4505) # endif #endif +#if defined(PYBIND11_CPP20) +# if __has_include() // __has_include has been part of C++17, no need to check it +# define PYBIND11_HAS_RANGES +# endif +#endif + #include #if PY_VERSION_HEX < 0x03080000 # error "PYTHON < 3.8 IS UNSUPPORTED. pybind11 v2.13 was the last to support Python 3.7." diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index f26c307a87..1e76d7bc13 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -1259,6 +1259,7 @@ class sequence_fast_readonly { using pointer = arrow_proxy; 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; } @@ -1281,6 +1282,7 @@ class sequence_slow_readwrite { using pointer = arrow_proxy; sequence_slow_readwrite(handle obj, ssize_t index) : obj(obj), index(index) {} + sequence_slow_readwrite() = default; reference dereference() const { return {obj, static_cast(index)}; } void increment() { ++index; } diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index ecb44939aa..45c8070f9e 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -12,6 +12,9 @@ #include "pybind11_tests.h" #include +#if defined(PYBIND11_HAS_RANGES) +# include +#endif namespace external { namespace detail { @@ -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; + 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{} && + 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); + for (auto it : tpl | std::views::transform( + [](auto& o) { return py::cast(o) + 1; })) { + py::print(it); + } + }); + m.def("transform_list_plus_one", [](py::list& lst) { + static_assert(std::ranges::random_access_range); + for (auto it : lst | std::views::transform( + [](auto& o) { return py::cast(o) + 1; })) { + py::print(it); + } + }); + m.def("transform_dict_plus_one", [](py::dict& dct) { + static_assert(std::ranges::forward_range); + for (auto it : dct | std::views::transform([](auto& o) { + return std::pair{py::cast(o.first) + 1, py::cast(o.second) + 1}; + })) { + py::print("{} : {}"_s.format(it.first, it.second)); + } + }); +#else + m.attr("defined_PYBIND11_HAS_RANGES") = false; +#endif } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 218092b434..4bf93b3fec 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1048,3 +1048,45 @@ 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 not available." +) +def test_ranges(capture): + assert m.iterator_default_initialization + + 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 + """) + + From 74615a04105366647261da83f2980cd5df01b3cd Mon Sep 17 00:00:00 2001 From: ObeliskGate Date: Fri, 16 Aug 2024 12:16:33 +0800 Subject: [PATCH 2/8] fix: format the code --- include/pybind11/detail/common.h | 6 +++--- tests/test_pytypes.cpp | 32 ++++++++++++++++---------------- tests/test_pytypes.py | 17 +++++++++-------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 61304501ac..082e78ef53 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -272,9 +272,9 @@ PYBIND11_WARNING_DISABLE_MSVC(4505) #endif #if defined(PYBIND11_CPP20) -# if __has_include() // __has_include has been part of C++17, no need to check it -# define PYBIND11_HAS_RANGES -# endif +# if __has_include() // __has_include has been part of C++17, no need to check it +# define PYBIND11_HAS_RANGES +# endif #endif #include diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 45c8070f9e..1b6b05d2f4 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -13,7 +13,7 @@ #include #if defined(PYBIND11_HAS_RANGES) -# include +# include #endif namespace external { @@ -933,32 +933,32 @@ TEST_SUBMODULE(pytypes, m) { 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{} && - DictIterator{} == DictIterator{}); // value initialization result must compared as true + return py::bool_( + TupleIterator{} == TupleIterator{} && ListIterator{} == ListIterator{} + && DictIterator{} + == DictIterator{}); // value initialization result must compared as true }); - m.def("transform_tuple_plus_one", [](py::tuple& tpl) { + m.def("transform_tuple_plus_one", [](py::tuple &tpl) { static_assert(std::ranges::random_access_range); - for (auto it : tpl | std::views::transform( - [](auto& o) { return py::cast(o) + 1; })) { + for (auto it : tpl | std::views::transform([](auto &o) { return py::cast(o) + 1; })) { py::print(it); } }); - m.def("transform_list_plus_one", [](py::list& lst) { + m.def("transform_list_plus_one", [](py::list &lst) { static_assert(std::ranges::random_access_range); - for (auto it : lst | std::views::transform( - [](auto& o) { return py::cast(o) + 1; })) { + for (auto it : lst | std::views::transform([](auto &o) { return py::cast(o) + 1; })) { py::print(it); } }); - m.def("transform_dict_plus_one", [](py::dict& dct) { + m.def("transform_dict_plus_one", [](py::dict &dct) { static_assert(std::ranges::forward_range); - for (auto it : dct | std::views::transform([](auto& o) { - return std::pair{py::cast(o.first) + 1, py::cast(o.second) + 1}; - })) { - py::print("{} : {}"_s.format(it.first, it.second)); - } + for (auto it : dct | std::views::transform([](auto &o) { + return std::pair{py::cast(o.first) + 1, + py::cast(o.second) + 1}; + })) { + py::print("{} : {}"_s.format(it.first, it.second)); + } }); #else m.attr("defined_PYBIND11_HAS_RANGES") = false; diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 4bf93b3fec..19889b224b 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1049,9 +1049,9 @@ def test_typevar(doc): 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 not available." + not m.defined_PYBIND11_HAS_RANGES, reason="C++20 not available." ) def test_ranges(capture): assert m.iterator_default_initialization @@ -1060,12 +1060,13 @@ def test_ranges(capture): m.transform_tuple_plus_one((1, 2, 3)) assert ( - capture.unordered + capture.unordered == """ 2 3 4 - """) + """ + ) with capture: m.transform_list_plus_one([1, 2, 3]) @@ -1076,7 +1077,8 @@ def test_ranges(capture): 2 3 4 - """) + """ + ) with capture: m.transform_dict_plus_one({1: 2, 3: 4, 5: 6}) @@ -1087,6 +1089,5 @@ def test_ranges(capture): 2 : 3 4 : 5 6 : 7 - """) - - + """ + ) From f10c40fca965d04034a8f85d8fbb26818af49ca0 Mon Sep 17 00:00:00 2001 From: ObeliskGate Date: Fri, 16 Aug 2024 15:46:07 +0800 Subject: [PATCH 3/8] fix: disable `ranges` in clang < 16 --- include/pybind11/detail/common.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 082e78ef53..aa6c4c0544 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -273,7 +273,13 @@ PYBIND11_WARNING_DISABLE_MSVC(4505) #if defined(PYBIND11_CPP20) # if __has_include() // __has_include has been part of C++17, no need to check it -# define PYBIND11_HAS_RANGES +# if !defined(PYBIND11_COMPILER_CLANG) +# define PYBIND11_HAS_RANGES +# else +# if __clang_major__ >= 16 // llvm/llvm-project#52696 +# define PYBIND11_HAS_RANGES +# endif +# endif # endif #endif From 6793aa53a3d10807936a92e76e0b012dfdd37f2e Mon Sep 17 00:00:00 2001 From: ObeliskGate Date: Sun, 18 Aug 2024 11:53:00 +0800 Subject: [PATCH 4/8] refactor: move `` test macro to `test_pytypes.h` --- include/pybind11/detail/common.h | 12 ------------ tests/test_pytypes.cpp | 19 ++++++++++++++++--- tests/test_pytypes.py | 3 ++- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index aa6c4c0544..643fd33d7e 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -271,18 +271,6 @@ PYBIND11_WARNING_DISABLE_MSVC(4505) # endif #endif -#if defined(PYBIND11_CPP20) -# if __has_include() // __has_include has been part of C++17, no need to check it -# if !defined(PYBIND11_COMPILER_CLANG) -# define PYBIND11_HAS_RANGES -# else -# if __clang_major__ >= 16 // llvm/llvm-project#52696 -# define PYBIND11_HAS_RANGES -# endif -# endif -# endif -#endif - #include #if PY_VERSION_HEX < 0x03080000 # error "PYTHON < 3.8 IS UNSUPPORTED. pybind11 v2.13 was the last to support Python 3.7." diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 1b6b05d2f4..047bc9adec 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -12,7 +12,20 @@ #include "pybind11_tests.h" #include -#if defined(PYBIND11_HAS_RANGES) + +#if defined(PYBIND11_CPP20) +# if __has_include() // __has_include has been part of C++17, no need to check it +# if !defined(PYBIND11_COMPILER_CLANG) +# define PYBIND11_TEST_PYTYPES_HAS_RANGES +# else +# if __clang_major__ >= 16 // llvm/llvm-project#52696 +# define PYBIND11_TEST_PYTYPES_HAS_RANGES +# endif +# endif +# endif +#endif + +#if defined(PYBIND11_TEST_PYTYPES_HAS_RANGES) # include #endif @@ -927,8 +940,7 @@ TEST_SUBMODULE(pytypes, m) { 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; +#if defined(PYBIND11_TEST_PYTYPES_HAS_RANGES) // test_ranges m.def("iterator_default_initialization", []() { using TupleIterator = decltype(py::tuple{}.begin()); using ListIterator = decltype(py::list{}.begin()); @@ -960,6 +972,7 @@ TEST_SUBMODULE(pytypes, m) { py::print("{} : {}"_s.format(it.first, it.second)); } }); + m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = true; #else m.attr("defined_PYBIND11_HAS_RANGES") = false; #endif diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 19889b224b..47e0bd2fed 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1051,7 +1051,8 @@ def test_typevar(doc): @pytest.mark.skipif( - not m.defined_PYBIND11_HAS_RANGES, reason="C++20 not available." + not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, + reason=" not available.", ) def test_ranges(capture): assert m.iterator_default_initialization From db766ce4cc80c5942796596f27faf2ea880bf371 Mon Sep 17 00:00:00 2001 From: ObeliskGate Date: Sun, 18 Aug 2024 12:28:44 +0800 Subject: [PATCH 5/8] refactor: seperate `ranges` test into 3 funcs --- tests/test_pytypes.cpp | 50 ++++++++++++++++++++++---------- tests/test_pytypes.py | 66 ++++++++++++++++++++---------------------- 2 files changed, 66 insertions(+), 50 deletions(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 047bc9adec..6107e26f8c 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -940,40 +940,58 @@ TEST_SUBMODULE(pytypes, m) { m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = false; #endif -#if defined(PYBIND11_TEST_PYTYPES_HAS_RANGES) // test_ranges - 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{} - && DictIterator{} - == DictIterator{}); // value initialization result must compared as true +#if defined(PYBIND11_TEST_PYTYPES_HAS_RANGES) + + // test_tuple_ranges + m.def("tuple_iterator_default_initialization", []() { + using TupleIterator = decltype(std::declval().begin()); + static_assert(std::random_access_iterator); + return TupleIterator{} == TupleIterator{}; }); m.def("transform_tuple_plus_one", [](py::tuple &tpl) { - static_assert(std::ranges::random_access_range); + py::list ret{}; for (auto it : tpl | std::views::transform([](auto &o) { return py::cast(o) + 1; })) { - py::print(it); + ret.append(py::int_(it)); } + return ret; }); + + // test_list_ranges + m.def("list_iterator_default_initialization", []() { + using ListIterator = decltype(std::declval().begin()); + static_assert(std::random_access_iterator); + return ListIterator{} == ListIterator{}; + }); + m.def("transform_list_plus_one", [](py::list &lst) { - static_assert(std::ranges::random_access_range); + py::list ret{}; for (auto it : lst | std::views::transform([](auto &o) { return py::cast(o) + 1; })) { - py::print(it); + ret.append(py::int_(it)); } + return ret; }); + + // test_dict_ranges + m.def("dict_iterator_default_initialization", []() { + using DictIterator = decltype(std::declval().begin()); + static_assert(std::forward_iterator); + return DictIterator{} == DictIterator{}; + }); + m.def("transform_dict_plus_one", [](py::dict &dct) { - static_assert(std::ranges::forward_range); + py::list ret{}; for (auto it : dct | std::views::transform([](auto &o) { return std::pair{py::cast(o.first) + 1, py::cast(o.second) + 1}; })) { - py::print("{} : {}"_s.format(it.first, it.second)); + ret.append(py::make_tuple(py::int_(it.first), py::int_(it.second))); } + return ret; }); + m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = true; #else - m.attr("defined_PYBIND11_HAS_RANGES") = false; + m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false; #endif } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 47e0bd2fed..1c6335f757 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1054,41 +1054,39 @@ def test_typevar(doc): not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, reason=" not available.", ) -def test_ranges(capture): - assert m.iterator_default_initialization - - with capture: - m.transform_tuple_plus_one((1, 2, 3)) - - assert ( - capture.unordered - == """ - 2 - 3 - 4 - """ - ) +@pytest.mark.parametrize( + ("tested_tuple", "expected"), + [((1,), [2]), ((3, 4), [4, 5]), ((7, 8, 9), [8, 9, 10])], +) +def test_tuple_ranges(tested_tuple, expected): + assert m.tuple_iterator_default_initialization() + assert m.transform_tuple_plus_one(tested_tuple) == expected - with capture: - m.transform_list_plus_one([1, 2, 3]) - assert ( - capture.unordered - == """ - 2 - 3 - 4 - """ - ) +@pytest.mark.skipif( + not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, + reason=" not available.", +) +@pytest.mark.parametrize( + ("tested_list", "expected"), [([1], [2]), ([3, 4], [4, 5]), ([7, 8, 9], [8, 9, 10])] +) +def test_list_ranges(tested_list, expected): + assert m.list_iterator_default_initialization() + assert m.transform_list_plus_one(tested_list) == expected - with capture: - m.transform_dict_plus_one({1: 2, 3: 4, 5: 6}) - assert ( - capture.unordered - == """ - 2 : 3 - 4 : 5 - 6 : 7 - """ - ) +@pytest.mark.skipif( + not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, + reason=" not available.", +) +@pytest.mark.parametrize( + ("tested_dict", "expected"), + [ + ({1: 2}, [(2, 3)]), + ({3: 4, 5: 6}, [(4, 5), (6, 7)]), + ({7: 8, 9: 10, 11: 12}, [(8, 9), (10, 11), (12, 13)]), + ], +) +def test_dict_ranges(tested_dict, expected): + assert m.dict_iterator_default_initialization() + assert m.transform_dict_plus_one(tested_dict) == expected From f3069811d3828595cc4d15e29e4cba6b2ef646e7 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 21 Aug 2024 13:28:05 -0400 Subject: [PATCH 6/8] style: compress the if statement --- tests/test_pytypes.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 6107e26f8c..9c84113f98 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -13,22 +13,13 @@ #include -#if defined(PYBIND11_CPP20) -# if __has_include() // __has_include has been part of C++17, no need to check it -# if !defined(PYBIND11_COMPILER_CLANG) -# define PYBIND11_TEST_PYTYPES_HAS_RANGES -# else -# if __clang_major__ >= 16 // llvm/llvm-project#52696 -# define PYBIND11_TEST_PYTYPES_HAS_RANGES -# endif -# endif +#if defined(PYBIND11_CPP20) && __has_include() // __has_include has been part of C++17, no need to check it +# if !defined(PYBIND11_COMPILER_CLANG) || __clang_major__ >= 16 // llvm/llvm-project#52696 +# define PYBIND11_TEST_PYTYPES_HAS_RANGES +# include # endif #endif -#if defined(PYBIND11_TEST_PYTYPES_HAS_RANGES) -# include -#endif - namespace external { namespace detail { bool check(PyObject *o) { return PyFloat_Check(o) != 0; } From 26efded026700f37910782a56b21aa7298904971 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:30:23 +0000 Subject: [PATCH 7/8] style: pre-commit fixes --- tests/test_pytypes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 9c84113f98..a9d4b4ff28 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -13,7 +13,8 @@ #include -#if defined(PYBIND11_CPP20) && __has_include() // __has_include has been part of C++17, no need to check it +#if defined(PYBIND11_CPP20) \ + && __has_include() // __has_include has been part of C++17, no need to check it # if !defined(PYBIND11_COMPILER_CLANG) || __clang_major__ >= 16 // llvm/llvm-project#52696 # define PYBIND11_TEST_PYTYPES_HAS_RANGES # include From 6bb210fe0d616d182edb2c8985a848943b39855f Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 21 Aug 2024 13:35:55 -0400 Subject: [PATCH 8/8] style: better formatting --- tests/test_pytypes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index a9d4b4ff28..19f65ce7eb 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -13,8 +13,8 @@ #include -#if defined(PYBIND11_CPP20) \ - && __has_include() // __has_include has been part of C++17, no need to check it +//__has_include has been part of C++17, no need to check it +#if defined(PYBIND11_CPP20) && __has_include() # if !defined(PYBIND11_COMPILER_CLANG) || __clang_major__ >= 16 // llvm/llvm-project#52696 # define PYBIND11_TEST_PYTYPES_HAS_RANGES # include