Skip to content

Commit 3c061f2

Browse files
committed
Fixing pybind11::bytes() ambiguous conversion issue.
Adding missing `bytes` type to `test_constructors()`, to exercise the code change. The changes in the PR were cherry-picked from PR #2409 (with a very minor modification in test_pytypes.py related to flake8). Via PR #2409, these changes were extensively tested in the Google environment, as summarized here: https://docs.google.com/document/d/1TPL-J__mph_yHa1quDvsO12E_F5OZnvBaZlW9IIrz8M/ The changes in this PR did not cause an issues at all. Note that `test_constructors()` before this PR passes for Python 2 only because `pybind11::str` can hold `PyUnicodeObject` or `PyBytesObject`. As a side-effect of this PR, `test_constructors()` no longer relies on this permissive `pybind11::str` behavior. However, the permissive behavior is still exercised/exposed via the existing `test_pybind11_str_raw_str()`. The test code change is designed to enable easy removal later, when Python 2 support is dropped. For completeness: confusingly, the non-test code changes travelled through PR Example `ambiguous conversion` error fixed by this PR: ``` pybind11/tests/test_pytypes.cpp:214:23: error: ambiguous conversion for functional-style cast from 'pybind11::detail::item_accessor' (aka 'accessor<accessor_policies::generic_item>') to 'py::bytes' "bytes"_a=py::bytes(d["bytes"]), ^~~~~~~~~~~~~~~~~~~~ pybind11/include/pybind11/detail/../pytypes.h:957:21: note: candidate constructor PYBIND11_OBJECT(bytes, object, PYBIND11_BYTES_CHECK) ^ pybind11/include/pybind11/detail/../pytypes.h:957:21: note: candidate constructor pybind11/include/pybind11/detail/../pytypes.h:987:15: note: candidate constructor inline bytes::bytes(const pybind11::str &s) { ^ 1 error generated. ```
1 parent 6a19278 commit 3c061f2

File tree

3 files changed

+19
-5
lines changed

3 files changed

+19
-5
lines changed

include/pybind11/pytypes.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,9 @@ PYBIND11_NAMESPACE_END(detail)
796796
Name(handle h, stolen_t) : Parent(h, stolen_t{}) { } \
797797
PYBIND11_DEPRECATED("Use py::isinstance<py::python_type>(obj) instead") \
798798
bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); } \
799-
static bool check_(handle h) { return h.ptr() != nullptr && CheckFun(h.ptr()); }
799+
static bool check_(handle h) { return h.ptr() != nullptr && CheckFun(h.ptr()); } \
800+
template <typename Policy_> \
801+
Name(const ::pybind11::detail::accessor<Policy_> &a) : Name(object(a)) { }
800802

801803
#define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \
802804
PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \
@@ -806,9 +808,7 @@ PYBIND11_NAMESPACE_END(detail)
806808
{ if (!m_ptr) throw error_already_set(); } \
807809
Name(object &&o) \
808810
: Parent(check_(o) ? o.release().ptr() : ConvertFun(o.ptr()), stolen_t{}) \
809-
{ if (!m_ptr) throw error_already_set(); } \
810-
template <typename Policy_> \
811-
Name(const ::pybind11::detail::accessor<Policy_> &a) : Name(object(a)) { }
811+
{ if (!m_ptr) throw error_already_set(); }
812812

813813
#define PYBIND11_OBJECT(Name, Parent, CheckFun) \
814814
PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \

tests/test_pytypes.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ TEST_SUBMODULE(pytypes, m) {
197197
// test_constructors
198198
m.def("default_constructors", []() {
199199
return py::dict(
200+
"bytes"_a=py::bytes(),
200201
"str"_a=py::str(),
201202
"bool"_a=py::bool_(),
202203
"int"_a=py::int_(),
@@ -210,6 +211,7 @@ TEST_SUBMODULE(pytypes, m) {
210211

211212
m.def("converting_constructors", [](py::dict d) {
212213
return py::dict(
214+
"bytes"_a=py::bytes(d["bytes"]),
213215
"str"_a=py::str(d["str"]),
214216
"bool"_a=py::bool_(d["bool"]),
215217
"int"_a=py::int_(d["int"]),
@@ -225,6 +227,7 @@ TEST_SUBMODULE(pytypes, m) {
225227
m.def("cast_functions", [](py::dict d) {
226228
// When converting between Python types, obj.cast<T>() should be the same as T(obj)
227229
return py::dict(
230+
"bytes"_a=d["bytes"].cast<py::bytes>(),
228231
"str"_a=d["str"].cast<py::str>(),
229232
"bool"_a=d["bool"].cast<py::bool_>(),
230233
"int"_a=d["int"].cast<py::int_>(),

tests/test_pytypes.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,17 @@ def func(self, x, *args):
190190

191191
def test_constructors():
192192
"""C++ default and converting constructors are equivalent to type calls in Python"""
193-
types = [str, bool, int, float, tuple, list, dict, set]
193+
types = [bytes, str, bool, int, float, tuple, list, dict, set]
194194
expected = {t.__name__: t() for t in types}
195+
if env.PY2:
196+
# Note that bytes.__name__ == 'str' in Python 2.
197+
# pybind11::str is unicode even under Python 2.
198+
expected["bytes"] = bytes()
199+
expected["str"] = unicode() # noqa: F821
195200
assert m.default_constructors() == expected
196201

197202
data = {
203+
bytes: b'41', # Currently no supported or working conversions.
198204
str: 42,
199205
bool: "Not empty",
200206
int: "42",
@@ -207,6 +213,11 @@ def test_constructors():
207213
}
208214
inputs = {k.__name__: v for k, v in data.items()}
209215
expected = {k.__name__: k(v) for k, v in data.items()}
216+
if env.PY2: # Similar to the above. See comments above.
217+
inputs["bytes"] = b'41'
218+
inputs["str"] = 42
219+
expected["bytes"] = b'41'
220+
expected["str"] = u"42"
210221

211222
assert m.converting_constructors(inputs) == expected
212223
assert m.cast_functions(inputs) == expected

0 commit comments

Comments
 (0)