nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at python-updates 452 lines 17 kB view raw
1diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h 2index 71bc5902..6a148e74 100644 3--- a/include/pybind11/stl.h 4+++ b/include/pybind11/stl.h 5@@ -11,10 +11,14 @@ 6 7 #include "pybind11.h" 8 #include "detail/common.h" 9+#include "detail/descr.h" 10+#include "detail/type_caster_base.h" 11 12 #include <deque> 13+#include <initializer_list> 14 #include <list> 15 #include <map> 16+#include <memory> 17 #include <ostream> 18 #include <set> 19 #include <unordered_map> 20@@ -35,6 +39,89 @@ 21 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) 22 PYBIND11_NAMESPACE_BEGIN(detail) 23 24+// 25+// Begin: Equivalent of 26+// https://github.com/google/clif/blob/ae4eee1de07cdf115c0c9bf9fec9ff28efce6f6c/clif/python/runtime.cc#L388-L438 27+/* 28+The three `PyObjectTypeIsConvertibleTo*()` functions below are 29+the result of converging the behaviors of pybind11 and PyCLIF 30+(http://github.com/google/clif). 31+ 32+Originally PyCLIF was extremely far on the permissive side of the spectrum, 33+while pybind11 was very far on the strict side. Originally PyCLIF accepted any 34+Python iterable as input for a C++ `vector`/`set`/`map` argument, as long as 35+the elements were convertible. The obvious (in hindsight) problem was that 36+any empty Python iterable could be passed to any of these C++ types, e.g. `{}` 37+was accepted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments. 38+ 39+The functions below strike a practical permissive-vs-strict compromise, 40+informed by tens of thousands of use cases in the wild. A main objective is 41+to prevent accidents and improve readability: 42+ 43+- Python literals must match the C++ types. 44+ 45+- For C++ `set`: The potentially reducing conversion from a Python sequence 46+ (e.g. Python `list` or `tuple`) to a C++ `set` must be explicit, by going 47+ through a Python `set`. 48+ 49+- However, a Python `set` can still be passed to a C++ `vector`. The rationale 50+ is that this conversion is not reducing. Implicit conversions of this kind 51+ are also fairly commonly used, therefore enforcing explicit conversions 52+ would have an unfavorable cost : benefit ratio; more sloppily speaking, 53+ such an enforcement would be more annoying than helpful. 54+*/ 55+ 56+inline bool PyObjectIsInstanceWithOneOfTpNames(PyObject *obj, 57+ std::initializer_list<const char *> tp_names) { 58+ if (PyType_Check(obj)) { 59+ return false; 60+ } 61+ const char *obj_tp_name = Py_TYPE(obj)->tp_name; 62+ for (const auto *tp_name : tp_names) { 63+ if (std::strcmp(obj_tp_name, tp_name) == 0) { 64+ return true; 65+ } 66+ } 67+ return false; 68+} 69+ 70+inline bool PyObjectTypeIsConvertibleToStdVector(PyObject *obj) { 71+ if (PySequence_Check(obj) != 0) { 72+ return !PyUnicode_Check(obj) && !PyBytes_Check(obj); 73+ } 74+ return (PyGen_Check(obj) != 0) || (PyAnySet_Check(obj) != 0) 75+ || PyObjectIsInstanceWithOneOfTpNames( 76+ obj, {"dict_keys", "dict_values", "dict_items", "map", "zip"}); 77+} 78+ 79+inline bool PyObjectTypeIsConvertibleToStdSet(PyObject *obj) { 80+ return (PyAnySet_Check(obj) != 0) || PyObjectIsInstanceWithOneOfTpNames(obj, {"dict_keys"}); 81+} 82+ 83+inline bool PyObjectTypeIsConvertibleToStdMap(PyObject *obj) { 84+ if (PyDict_Check(obj)) { 85+ return true; 86+ } 87+ // Implicit requirement in the conditions below: 88+ // A type with `.__getitem__()` & `.items()` methods must implement these 89+ // to be compatible with https://docs.python.org/3/c-api/mapping.html 90+ if (PyMapping_Check(obj) == 0) { 91+ return false; 92+ } 93+ PyObject *items = PyObject_GetAttrString(obj, "items"); 94+ if (items == nullptr) { 95+ PyErr_Clear(); 96+ return false; 97+ } 98+ bool is_convertible = (PyCallable_Check(items) != 0); 99+ Py_DECREF(items); 100+ return is_convertible; 101+} 102+ 103+// 104+// End: Equivalent of clif/python/runtime.cc 105+// 106+ 107 /// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for 108 /// forwarding a container element). Typically used indirect via forwarded_type(), below. 109 template <typename T, typename U> 110@@ -66,17 +153,10 @@ private: 111 } 112 void reserve_maybe(const anyset &, void *) {} 113 114-public: 115- bool load(handle src, bool convert) { 116- if (!isinstance<anyset>(src)) { 117- return false; 118- } 119- auto s = reinterpret_borrow<anyset>(src); 120- value.clear(); 121- reserve_maybe(s, &value); 122- for (auto entry : s) { 123+ bool convert_iterable(const iterable &itbl, bool convert) { 124+ for (const auto &it : itbl) { 125 key_conv conv; 126- if (!conv.load(entry, convert)) { 127+ if (!conv.load(it, convert)) { 128 return false; 129 } 130 value.insert(cast_op<Key &&>(std::move(conv))); 131@@ -84,6 +164,29 @@ public: 132 return true; 133 } 134 135+ bool convert_anyset(anyset s, bool convert) { 136+ value.clear(); 137+ reserve_maybe(s, &value); 138+ return convert_iterable(s, convert); 139+ } 140+ 141+public: 142+ bool load(handle src, bool convert) { 143+ if (!PyObjectTypeIsConvertibleToStdSet(src.ptr())) { 144+ return false; 145+ } 146+ if (isinstance<anyset>(src)) { 147+ value.clear(); 148+ return convert_anyset(reinterpret_borrow<anyset>(src), convert); 149+ } 150+ if (!convert) { 151+ return false; 152+ } 153+ assert(isinstance<iterable>(src)); 154+ value.clear(); 155+ return convert_iterable(reinterpret_borrow<iterable>(src), convert); 156+ } 157+ 158 template <typename T> 159 static handle cast(T &&src, return_value_policy policy, handle parent) { 160 if (!std::is_lvalue_reference<T>::value) { 161@@ -115,15 +218,10 @@ private: 162 } 163 void reserve_maybe(const dict &, void *) {} 164 165-public: 166- bool load(handle src, bool convert) { 167- if (!isinstance<dict>(src)) { 168- return false; 169- } 170- auto d = reinterpret_borrow<dict>(src); 171+ bool convert_elements(const dict &d, bool convert) { 172 value.clear(); 173 reserve_maybe(d, &value); 174- for (auto it : d) { 175+ for (const auto &it : d) { 176 key_conv kconv; 177 value_conv vconv; 178 if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) { 179@@ -134,6 +232,25 @@ public: 180 return true; 181 } 182 183+public: 184+ bool load(handle src, bool convert) { 185+ if (!PyObjectTypeIsConvertibleToStdMap(src.ptr())) { 186+ return false; 187+ } 188+ if (isinstance<dict>(src)) { 189+ return convert_elements(reinterpret_borrow<dict>(src), convert); 190+ } 191+ if (!convert) { 192+ return false; 193+ } 194+ auto items = reinterpret_steal<object>(PyMapping_Items(src.ptr())); 195+ if (!items) { 196+ throw error_already_set(); 197+ } 198+ assert(isinstance<iterable>(items)); 199+ return convert_elements(dict(reinterpret_borrow<iterable>(items)), convert); 200+ } 201+ 202 template <typename T> 203 static handle cast(T &&src, return_value_policy policy, handle parent) { 204 dict d; 205@@ -166,13 +283,35 @@ struct list_caster { 206 using value_conv = make_caster<Value>; 207 208 bool load(handle src, bool convert) { 209- if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) { 210+ if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) { 211 return false; 212 } 213- auto s = reinterpret_borrow<sequence>(src); 214+ if (isinstance<sequence>(src)) { 215+ return convert_elements(src, convert); 216+ } 217+ if (!convert) { 218+ return false; 219+ } 220+ // Designed to be behavior-equivalent to passing tuple(src) from Python: 221+ // The conversion to a tuple will first exhaust the generator object, to ensure that 222+ // the generator is not left in an unpredictable (to the caller) partially-consumed 223+ // state. 224+ assert(isinstance<iterable>(src)); 225+ return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert); 226+ } 227+ 228+private: 229+ template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0> 230+ void reserve_maybe(const sequence &s, Type *) { 231+ value.reserve(s.size()); 232+ } 233+ void reserve_maybe(const sequence &, void *) {} 234+ 235+ bool convert_elements(handle seq, bool convert) { 236+ auto s = reinterpret_borrow<sequence>(seq); 237 value.clear(); 238 reserve_maybe(s, &value); 239- for (const auto &it : s) { 240+ for (const auto &it : seq) { 241 value_conv conv; 242 if (!conv.load(it, convert)) { 243 return false; 244@@ -182,13 +321,6 @@ struct list_caster { 245 return true; 246 } 247 248-private: 249- template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0> 250- void reserve_maybe(const sequence &s, Type *) { 251- value.reserve(s.size()); 252- } 253- void reserve_maybe(const sequence &, void *) {} 254- 255 public: 256 template <typename T> 257 static handle cast(T &&src, return_value_policy policy, handle parent) { 258@@ -220,43 +352,87 @@ struct type_caster<std::deque<Type, Alloc>> : list_caster<std::deque<Type, Alloc 259 template <typename Type, typename Alloc> 260 struct type_caster<std::list<Type, Alloc>> : list_caster<std::list<Type, Alloc>, Type> {}; 261 262+template <typename ArrayType, typename V, size_t... I> 263+ArrayType vector_to_array_impl(V &&v, index_sequence<I...>) { 264+ return {{std::move(v[I])...}}; 265+} 266+ 267+// Based on https://en.cppreference.com/w/cpp/container/array/to_array 268+template <typename ArrayType, size_t N, typename V> 269+ArrayType vector_to_array(V &&v) { 270+ return vector_to_array_impl<ArrayType, V>(std::forward<V>(v), make_index_sequence<N>{}); 271+} 272+ 273 template <typename ArrayType, typename Value, bool Resizable, size_t Size = 0> 274 struct array_caster { 275 using value_conv = make_caster<Value>; 276 277 private: 278- template <bool R = Resizable> 279- bool require_size(enable_if_t<R, size_t> size) { 280- if (value.size() != size) { 281- value.resize(size); 282+ std::unique_ptr<ArrayType> value; 283+ 284+ template <bool R = Resizable, enable_if_t<R, int> = 0> 285+ bool convert_elements(handle seq, bool convert) { 286+ auto l = reinterpret_borrow<sequence>(seq); 287+ value.reset(new ArrayType{}); 288+ // Using `resize` to preserve the behavior exactly as it was before PR #5305 289+ // For the `resize` to work, `Value` must be default constructible. 290+ // For `std::valarray`, this is a requirement: 291+ // https://en.cppreference.com/w/cpp/named_req/NumericType 292+ value->resize(l.size()); 293+ size_t ctr = 0; 294+ for (const auto &it : l) { 295+ value_conv conv; 296+ if (!conv.load(it, convert)) { 297+ return false; 298+ } 299+ (*value)[ctr++] = cast_op<Value &&>(std::move(conv)); 300 } 301 return true; 302 } 303- template <bool R = Resizable> 304- bool require_size(enable_if_t<!R, size_t> size) { 305- return size == Size; 306- } 307 308-public: 309- bool load(handle src, bool convert) { 310- if (!isinstance<sequence>(src)) { 311+ template <bool R = Resizable, enable_if_t<!R, int> = 0> 312+ bool convert_elements(handle seq, bool convert) { 313+ auto l = reinterpret_borrow<sequence>(seq); 314+ if (l.size() != Size) { 315 return false; 316 } 317- auto l = reinterpret_borrow<sequence>(src); 318- if (!require_size(l.size())) { 319- return false; 320- } 321- size_t ctr = 0; 322- for (const auto &it : l) { 323+ // The `temp` storage is needed to support `Value` types that are not 324+ // default-constructible. 325+ // Deliberate choice: no template specializations, for simplicity, and 326+ // because the compile time overhead for the specializations is deemed 327+ // more significant than the runtime overhead for the `temp` storage. 328+ std::vector<Value> temp; 329+ temp.reserve(l.size()); 330+ for (auto it : l) { 331 value_conv conv; 332 if (!conv.load(it, convert)) { 333 return false; 334 } 335- value[ctr++] = cast_op<Value &&>(std::move(conv)); 336+ temp.emplace_back(cast_op<Value &&>(std::move(conv))); 337 } 338+ value.reset(new ArrayType(vector_to_array<ArrayType, Size>(std::move(temp)))); 339 return true; 340 } 341 342+public: 343+ bool load(handle src, bool convert) { 344+ if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) { 345+ return false; 346+ } 347+ if (isinstance<sequence>(src)) { 348+ return convert_elements(src, convert); 349+ } 350+ if (!convert) { 351+ return false; 352+ } 353+ // Designed to be behavior-equivalent to passing tuple(src) from Python: 354+ // The conversion to a tuple will first exhaust the generator object, to ensure that 355+ // the generator is not left in an unpredictable (to the caller) partially-consumed 356+ // state. 357+ assert(isinstance<iterable>(src)); 358+ return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert); 359+ } 360+ 361 template <typename T> 362 static handle cast(T &&src, return_value_policy policy, handle parent) { 363 list l(src.size()); 364@@ -272,12 +448,36 @@ public: 365 return l.release(); 366 } 367 368- PYBIND11_TYPE_CASTER(ArrayType, 369- const_name<Resizable>(const_name(""), const_name("Annotated[")) 370- + const_name("list[") + value_conv::name + const_name("]") 371- + const_name<Resizable>(const_name(""), 372- const_name(", FixedSize(") 373- + const_name<Size>() + const_name(")]"))); 374+ // Code copied from PYBIND11_TYPE_CASTER macro. 375+ // Intentionally preserving the behavior exactly as it was before PR #5305 376+ template <typename T_, enable_if_t<std::is_same<ArrayType, remove_cv_t<T_>>::value, int> = 0> 377+ static handle cast(T_ *src, return_value_policy policy, handle parent) { 378+ if (!src) { 379+ return none().release(); 380+ } 381+ if (policy == return_value_policy::take_ownership) { 382+ auto h = cast(std::move(*src), policy, parent); 383+ delete src; // WARNING: Assumes `src` was allocated with `new`. 384+ return h; 385+ } 386+ return cast(*src, policy, parent); 387+ } 388+ 389+ // NOLINTNEXTLINE(google-explicit-constructor) 390+ operator ArrayType *() { return &(*value); } 391+ // NOLINTNEXTLINE(google-explicit-constructor) 392+ operator ArrayType &() { return *value; } 393+ // NOLINTNEXTLINE(google-explicit-constructor) 394+ operator ArrayType &&() && { return std::move(*value); } 395+ 396+ template <typename T_> 397+ using cast_op_type = movable_cast_op_type<T_>; 398+ 399+ static constexpr auto name 400+ = const_name<Resizable>(const_name(""), const_name("Annotated[")) + const_name("list[") 401+ + value_conv::name + const_name("]") 402+ + const_name<Resizable>( 403+ const_name(""), const_name(", FixedSize(") + const_name<Size>() + const_name(")]")); 404 }; 405 406 template <typename Type, size_t Size> 407diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp 408index e7db8aaa..84bd4755 100644 409--- a/tests/test_stl.cpp 410+++ b/tests/test_stl.cpp 411@@ -193,6 +193,23 @@ TEST_SUBMODULE(stl, m) { 412 m.def("cast_array", []() { return std::array<int, 2>{{1, 2}}; }); 413 m.def("load_array", [](const std::array<int, 2> &a) { return a[0] == 1 && a[1] == 2; }); 414 415+ struct NoDefaultCtor { 416+ explicit constexpr NoDefaultCtor(int val) : val{val} {} 417+ int val; 418+ }; 419+ 420+ struct NoDefaultCtorArray { 421+ explicit constexpr NoDefaultCtorArray(int i) 422+ : arr{{NoDefaultCtor(10 + i), NoDefaultCtor(20 + i)}} {} 423+ std::array<NoDefaultCtor, 2> arr; 424+ }; 425+ 426+ // test_array_no_default_ctor 427+ py::class_<NoDefaultCtor>(m, "NoDefaultCtor").def_readonly("val", &NoDefaultCtor::val); 428+ py::class_<NoDefaultCtorArray>(m, "NoDefaultCtorArray") 429+ .def(py::init<int>()) 430+ .def_readwrite("arr", &NoDefaultCtorArray::arr); 431+ 432 // test_valarray 433 m.def("cast_valarray", []() { return std::valarray<int>{1, 4, 9}; }); 434 m.def("load_valarray", [](const std::valarray<int> &v) { 435diff --git a/tests/test_stl.py b/tests/test_stl.py 436index 65fda54c..340cdc35 100644 437--- a/tests/test_stl.py 438+++ b/tests/test_stl.py 439@@ -48,6 +48,13 @@ def test_array(doc): 440 ) 441 442 443+def test_array_no_default_ctor(): 444+ lst = m.NoDefaultCtorArray(3) 445+ assert [e.val for e in lst.arr] == [13, 23] 446+ lst.arr = m.NoDefaultCtorArray(4).arr 447+ assert [e.val for e in lst.arr] == [14, 24] 448+ 449+ 450 def test_valarray(doc): 451 """std::valarray <-> list""" 452 lst = m.cast_valarray()