1313#include " detail/common.h"
1414
1515#include < deque>
16+ #include < initializer_list>
1617#include < list>
1718#include < map>
1819#include < ostream>
3536PYBIND11_NAMESPACE_BEGIN (PYBIND11_NAMESPACE)
3637PYBIND11_NAMESPACE_BEGIN(detail)
3738
39+ //
40+ // Begin: Equivalent of
41+ // https://github.com/google/clif/blob/ae4eee1de07cdf115c0c9bf9fec9ff28efce6f6c/clif/python/runtime.cc#L388-L438
42+ /*
43+ The three `PyObjectTypeIsConvertibleTo*()` functions below are
44+ the result of converging the behaviors of pybind11 and PyCLIF
45+ (http://github.com/google/clif).
46+
47+ Originally PyCLIF was extremely far on the permissive side of the spectrum,
48+ while pybind11 was very far on the strict side. Originally PyCLIF accepted any
49+ Python iterable as input for a C++ `vector`/`set`/`map` argument, as long as
50+ the elements were convertible. The obvious (in hindsight) problem was that
51+ any empty Python iterable could be passed to any of these C++ types, e.g. `{}`
52+ was accepted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments.
53+
54+ The functions below strike a practical permissive-vs-strict compromise,
55+ informed by tens of thousands of use cases in the wild. A main objective is
56+ to prevent accidents and improve readability:
57+
58+ - Python literals must match the C++ types.
59+
60+ - For C++ `set`: The potentially reducing conversion from a Python sequence
61+ (e.g. Python `list` or `tuple`) to a C++ `set` must be explicit, by going
62+ through a Python `set`.
63+
64+ - However, a Python `set` can still be passed to a C++ `vector`. The rationale
65+ is that this conversion is not reducing. Implicit conversions of this kind
66+ are also fairly commonly used, therefore enforcing explicit conversions
67+ would have an unfavorable cost : benefit ratio; more sloppily speaking,
68+ such an enforcement would be more annoying than helpful.
69+ */
70+
71+ inline bool PyObjectIsInstanceWithOneOfTpNames(PyObject *obj,
72+ std::initializer_list<const char *> tp_names) {
73+ if (PyType_Check (obj)) {
74+ return false ;
75+ }
76+ const char *obj_tp_name = Py_TYPE (obj)->tp_name ;
77+ for (const auto *tp_name : tp_names) {
78+ if (std::strcmp (obj_tp_name, tp_name) == 0 ) {
79+ return true ;
80+ }
81+ }
82+ return false ;
83+ }
84+
85+ inline bool PyObjectTypeIsConvertibleToStdVector (PyObject *obj) {
86+ if (PySequence_Check (obj) != 0 ) {
87+ return !PyUnicode_Check (obj) && !PyBytes_Check (obj);
88+ }
89+ return (PyGen_Check (obj) != 0 ) || (PyAnySet_Check (obj) != 0 )
90+ || PyObjectIsInstanceWithOneOfTpNames (
91+ obj, {" dict_keys" , " dict_values" , " dict_items" , " map" , " zip" });
92+ }
93+
94+ inline bool PyObjectTypeIsConvertibleToStdSet (PyObject *obj) {
95+ return (PyAnySet_Check (obj) != 0 ) || PyObjectIsInstanceWithOneOfTpNames (obj, {" dict_keys" });
96+ }
97+
98+ inline bool PyObjectTypeIsConvertibleToStdMap (PyObject *obj) {
99+ if (PyDict_Check (obj)) {
100+ return true ;
101+ }
102+ // Implicit requirement in the conditions below:
103+ // A type with `.__getitem__()` & `.items()` methods must implement these
104+ // to be compatible with https://docs.python.org/3/c-api/mapping.html
105+ if (PyMapping_Check (obj) == 0 ) {
106+ return false ;
107+ }
108+ PyObject *items = PyObject_GetAttrString (obj, " items" );
109+ if (items == nullptr ) {
110+ PyErr_Clear ();
111+ return false ;
112+ }
113+ bool is_convertible = (PyCallable_Check (items) != 0 );
114+ Py_DECREF (items);
115+ return is_convertible;
116+ }
117+
118+ //
119+ // End: Equivalent of clif/python/runtime.cc
120+ //
121+
38122// / Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for
39123// / forwarding a container element). Typically used indirect via forwarded_type(), below.
40124template <typename T, typename U>
@@ -66,24 +150,40 @@ struct set_caster {
66150 }
67151 void reserve_maybe (const anyset &, void *) {}
68152
69- public:
70- bool load (handle src, bool convert) {
71- if (!isinstance<anyset>(src)) {
72- return false ;
73- }
74- auto s = reinterpret_borrow<anyset>(src);
75- value.clear ();
76- reserve_maybe (s, &value);
77- for (auto entry : s) {
153+ bool convert_iterable (const iterable &itbl, bool convert) {
154+ for (const auto &it : itbl) {
78155 key_conv conv;
79- if (!conv.load (entry , convert)) {
156+ if (!conv.load (it , convert)) {
80157 return false ;
81158 }
82159 value.insert (cast_op<Key &&>(std::move (conv)));
83160 }
84161 return true ;
85162 }
86163
164+ bool convert_anyset (anyset s, bool convert) {
165+ value.clear ();
166+ reserve_maybe (s, &value);
167+ return convert_iterable (s, convert);
168+ }
169+
170+ public:
171+ bool load (handle src, bool convert) {
172+ if (!PyObjectTypeIsConvertibleToStdSet (src.ptr ())) {
173+ return false ;
174+ }
175+ if (isinstance<anyset>(src)) {
176+ value.clear ();
177+ return convert_anyset (reinterpret_borrow<anyset>(src), convert);
178+ }
179+ if (!convert) {
180+ return false ;
181+ }
182+ assert (isinstance<iterable>(src));
183+ value.clear ();
184+ return convert_iterable (reinterpret_borrow<iterable>(src), convert);
185+ }
186+
87187 template <typename T>
88188 static handle cast (T &&src, return_value_policy policy, handle parent) {
89189 if (!std::is_lvalue_reference<T>::value) {
@@ -115,15 +215,10 @@ struct map_caster {
115215 }
116216 void reserve_maybe (const dict &, void *) {}
117217
118- public:
119- bool load (handle src, bool convert) {
120- if (!isinstance<dict>(src)) {
121- return false ;
122- }
123- auto d = reinterpret_borrow<dict>(src);
218+ bool convert_elements (const dict &d, bool convert) {
124219 value.clear ();
125220 reserve_maybe (d, &value);
126- for (auto it : d) {
221+ for (const auto & it : d) {
127222 key_conv kconv;
128223 value_conv vconv;
129224 if (!kconv.load (it.first .ptr (), convert) || !vconv.load (it.second .ptr (), convert)) {
@@ -134,6 +229,25 @@ struct map_caster {
134229 return true ;
135230 }
136231
232+ public:
233+ bool load (handle src, bool convert) {
234+ if (!PyObjectTypeIsConvertibleToStdMap (src.ptr ())) {
235+ return false ;
236+ }
237+ if (isinstance<dict>(src)) {
238+ return convert_elements (reinterpret_borrow<dict>(src), convert);
239+ }
240+ if (!convert) {
241+ return false ;
242+ }
243+ auto items = reinterpret_steal<object>(PyMapping_Items (src.ptr ()));
244+ if (!items) {
245+ throw error_already_set ();
246+ }
247+ assert (isinstance<iterable>(items));
248+ return convert_elements (dict (reinterpret_borrow<iterable>(items)), convert);
249+ }
250+
137251 template <typename T>
138252 static handle cast (T &&src, return_value_policy policy, handle parent) {
139253 dict d;
@@ -166,13 +280,35 @@ struct list_caster {
166280 using value_conv = make_caster<Value>;
167281
168282 bool load (handle src, bool convert) {
169- if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) {
283+ if (!PyObjectTypeIsConvertibleToStdVector (src.ptr ())) {
284+ return false ;
285+ }
286+ if (isinstance<sequence>(src)) {
287+ return convert_elements (src, convert);
288+ }
289+ if (!convert) {
170290 return false ;
171291 }
172- auto s = reinterpret_borrow<sequence>(src);
292+ // Designed to be behavior-equivalent to passing tuple(src) from Python:
293+ // The conversion to a tuple will first exhaust the generator object, to ensure that
294+ // the generator is not left in an unpredictable (to the caller) partially-consumed
295+ // state.
296+ assert (isinstance<iterable>(src));
297+ return convert_elements (tuple (reinterpret_borrow<iterable>(src)), convert);
298+ }
299+
300+ private:
301+ template <typename T = Type, enable_if_t <has_reserve_method<T>::value, int > = 0 >
302+ void reserve_maybe (const sequence &s, Type *) {
303+ value.reserve (s.size ());
304+ }
305+ void reserve_maybe (const sequence &, void *) {}
306+
307+ bool convert_elements (handle seq, bool convert) {
308+ auto s = reinterpret_borrow<sequence>(seq);
173309 value.clear ();
174310 reserve_maybe (s, &value);
175- for (const auto &it : s ) {
311+ for (const auto &it : seq ) {
176312 value_conv conv;
177313 if (!conv.load (it, convert)) {
178314 return false ;
@@ -182,13 +318,6 @@ struct list_caster {
182318 return true ;
183319 }
184320
185- private:
186- template <typename T = Type, enable_if_t <has_reserve_method<T>::value, int > = 0 >
187- void reserve_maybe (const sequence &s, Type *) {
188- value.reserve (s.size ());
189- }
190- void reserve_maybe (const sequence &, void *) {}
191-
192321public:
193322 template <typename T>
194323 static handle cast (T &&src, return_value_policy policy, handle parent) {
@@ -237,12 +366,8 @@ struct array_caster {
237366 return size == Size;
238367 }
239368
240- public:
241- bool load (handle src, bool convert) {
242- if (!isinstance<sequence>(src)) {
243- return false ;
244- }
245- auto l = reinterpret_borrow<sequence>(src);
369+ bool convert_elements (handle seq, bool convert) {
370+ auto l = reinterpret_borrow<sequence>(seq);
246371 if (!require_size (l.size ())) {
247372 return false ;
248373 }
@@ -257,6 +382,25 @@ struct array_caster {
257382 return true ;
258383 }
259384
385+ public:
386+ bool load (handle src, bool convert) {
387+ if (!PyObjectTypeIsConvertibleToStdVector (src.ptr ())) {
388+ return false ;
389+ }
390+ if (isinstance<sequence>(src)) {
391+ return convert_elements (src, convert);
392+ }
393+ if (!convert) {
394+ return false ;
395+ }
396+ // Designed to be behavior-equivalent to passing tuple(src) from Python:
397+ // The conversion to a tuple will first exhaust the generator object, to ensure that
398+ // the generator is not left in an unpredictable (to the caller) partially-consumed
399+ // state.
400+ assert (isinstance<iterable>(src));
401+ return convert_elements (tuple (reinterpret_borrow<iterable>(src)), convert);
402+ }
403+
260404 template <typename T>
261405 static handle cast (T &&src, return_value_policy policy, handle parent) {
262406 list l (src.size ());
0 commit comments