From 5b5547bc1bcfdf58ff4febbb3a08f1764ac4ec7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 17:57:55 -0400 Subject: [PATCH 1/2] chore(deps): bump ilammy/msvc-dev-cmd from 1.11.0 to 1.12.0 (#4242) Bumps [ilammy/msvc-dev-cmd](https://github.com/ilammy/msvc-dev-cmd) from 1.11.0 to 1.12.0. - [Release notes](https://github.com/ilammy/msvc-dev-cmd/releases) - [Commits](https://github.com/ilammy/msvc-dev-cmd/compare/v1.11.0...v1.12.0) --- updated-dependencies: - dependency-name: ilammy/msvc-dev-cmd dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38694bcfa..bc7175d82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -757,7 +757,7 @@ jobs: uses: jwlawson/actions-setup-cmake@v1.13 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.11.0 + uses: ilammy/msvc-dev-cmd@v1.12.0 with: arch: x86 @@ -810,7 +810,7 @@ jobs: uses: jwlawson/actions-setup-cmake@v1.13 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.11.0 + uses: ilammy/msvc-dev-cmd@v1.12.0 with: arch: x86 From b926396bdf42ef1e5f1f67f9d42277ce0c88adb8 Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Mon, 17 Oct 2022 19:15:08 -0400 Subject: [PATCH 2/2] bugfix: py contains raises errors when appropiate (#4209) * bugfix: contains now throws an exception if the key is not hashable * Fix tests and improve robustness * Remove todo * Workaround PyPy corner case * PyPy xfail * Fix typo * fix xfail * Make clang-tidy happy * Remove redundant exc checking --- include/pybind11/pytypes.h | 12 ++++++++++-- tests/test_pytypes.cpp | 5 ++++- tests/test_pytypes.py | 25 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 4b93d2018..37fc49a0e 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -1967,7 +1967,11 @@ public: void clear() /* py-non-const */ { PyDict_Clear(ptr()); } template bool contains(T &&key) const { - return PyDict_Contains(m_ptr, detail::object_or_cast(std::forward(key)).ptr()) == 1; + auto result = PyDict_Contains(m_ptr, detail::object_or_cast(std::forward(key)).ptr()); + if (result == -1) { + throw error_already_set(); + } + return result == 1; } private: @@ -2053,7 +2057,11 @@ public: bool empty() const { return size() == 0; } template bool contains(T &&val) const { - return PySet_Contains(m_ptr, detail::object_or_cast(std::forward(val)).ptr()) == 1; + auto result = PySet_Contains(m_ptr, detail::object_or_cast(std::forward(val)).ptr()); + if (result == -1) { + throw error_already_set(); + } + return result == 1; } }; diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index c95ff8230..99237ccef 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -183,7 +183,7 @@ TEST_SUBMODULE(pytypes, m) { return d2; }); m.def("dict_contains", - [](const py::dict &dict, py::object val) { return dict.contains(val); }); + [](const py::dict &dict, const py::object &val) { return dict.contains(val); }); m.def("dict_contains", [](const py::dict &dict, const char *val) { return dict.contains(val); }); @@ -538,6 +538,9 @@ TEST_SUBMODULE(pytypes, m) { m.def("hash_function", [](py::object obj) { return py::hash(std::move(obj)); }); + m.def("obj_contains", + [](py::object &obj, const py::object &key) { return obj.contains(key); }); + m.def("test_number_protocol", [](const py::object &a, const py::object &b) { py::list l; l.append(a.equal(b)); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 070849fc5..3ed7b9c94 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -168,6 +168,31 @@ def test_dict(capture, doc): assert m.dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3} +class CustomContains: + d = {"key": None} + + def __contains__(self, m): + return m in self.d + + +@pytest.mark.parametrize( + "arg,func", + [ + (set(), m.anyset_contains), + (dict(), m.dict_contains), + (CustomContains(), m.obj_contains), + ], +) +@pytest.mark.xfail("env.PYPY and sys.pypy_version_info < (7, 3, 10)", strict=False) +def test_unhashable_exceptions(arg, func): + class Unhashable: + __hash__ = None + + with pytest.raises(TypeError) as exc_info: + func(arg, Unhashable()) + assert "unhashable type:" in str(exc_info.value) + + def test_tuple(): assert m.tuple_no_args() == () assert m.tuple_ssize_t() == ()