diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03cb0238cd..214968572d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,7 +188,7 @@ jobs: allow-prereleases: true - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.0.0 with: enable-cache: true @@ -302,7 +302,7 @@ jobs: debug: ${{ matrix.python-debug }} - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.1 + uses: jwlawson/actions-setup-cmake@v2.2 - name: Valgrind cache if: matrix.valgrind @@ -570,7 +570,7 @@ jobs: run: python3 -m pip install --upgrade pip - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.1 + uses: jwlawson/actions-setup-cmake@v2.2 - name: Configure shell: bash @@ -906,7 +906,7 @@ jobs: ${{ matrix.python == '3.13' && runner.os == 'Windows' }} - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.1 + uses: jwlawson/actions-setup-cmake@v2.2 - name: Prepare MSVC uses: ilammy/msvc-dev-cmd@v1.13.0 @@ -956,7 +956,7 @@ jobs: architecture: x86 - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.1 + uses: jwlawson/actions-setup-cmake@v2.2 - name: Prepare MSVC uses: ilammy/msvc-dev-cmd@v1.13.0 @@ -1007,7 +1007,7 @@ jobs: run: python3 -m pip install -r tests/requirements.txt - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.1 + uses: jwlawson/actions-setup-cmake@v2.2 - name: Configure C++20 run: > @@ -1189,7 +1189,7 @@ jobs: python-version: ${{ matrix.python }} - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.1 + uses: jwlawson/actions-setup-cmake@v2.2 - name: Install ninja-build tool uses: seanmiddleditch/gha-setup-ninja@v6 diff --git a/.github/workflows/configure.yml b/.github/workflows/configure.yml index 931c0bff29..e849421146 100644 --- a/.github/workflows/configure.yml +++ b/.github/workflows/configure.yml @@ -56,7 +56,7 @@ jobs: python-version: 3.11 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.0.0 - name: Prepare env run: uv pip install --python=python --system -r tests/requirements.txt @@ -64,7 +64,7 @@ jobs: # An action for adding a specific version of CMake: # https://github.com/jwlawson/actions-setup-cmake - name: Setup CMake ${{ matrix.cmake }} - uses: jwlawson/actions-setup-cmake@v2.1 + uses: jwlawson/actions-setup-cmake@v2.2 with: cmake-version: ${{ matrix.cmake }} diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml index 98211ca552..d7ab9b0413 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.0.0 - name: Build SDist and wheels run: | diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 981884a955..c52857cff5 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -31,7 +31,7 @@ jobs: python-version: 3.8 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.0.0 - name: Prepare env run: uv pip install --system -r tests/requirements.txt @@ -55,7 +55,7 @@ jobs: python-version: 3.8 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.0.0 - name: Prepare env run: uv pip install --system -r tests/requirements.txt twine nox diff --git a/.github/workflows/reusable-standard.yml b/.github/workflows/reusable-standard.yml index 6e22d0f38b..e53c27551f 100644 --- a/.github/workflows/reusable-standard.yml +++ b/.github/workflows/reusable-standard.yml @@ -51,7 +51,7 @@ jobs: run: brew install boost - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.0.0 with: enable-cache: true diff --git a/.github/workflows/tests-cibw.yml b/.github/workflows/tests-cibw.yml index bf534316af..f7bca76e77 100644 --- a/.github/workflows/tests-cibw.yml +++ b/.github/workflows/tests-cibw.yml @@ -22,7 +22,7 @@ jobs: submodules: true fetch-depth: 0 - - uses: pypa/cibuildwheel@v3.3 + - uses: pypa/cibuildwheel@v3.4 env: PYODIDE_BUILD_EXPORTS: whole_archive with: @@ -45,7 +45,7 @@ jobs: # We have to uninstall first because GH is now using a local tap to build cmake<4, iOS needs cmake>=4 - run: brew uninstall cmake && brew install cmake - - uses: pypa/cibuildwheel@v3.3 + - uses: pypa/cibuildwheel@v3.4 env: CIBW_PLATFORM: ios CIBW_SKIP: cp314-* # https://github.com/pypa/cibuildwheel/issues/2494 @@ -70,7 +70,7 @@ jobs: if: contains(matrix.runs-on, 'macos') run: echo "CIBW_TEST_COMMAND=" >> "$GITHUB_ENV" - - uses: pypa/cibuildwheel@v3.3 + - uses: pypa/cibuildwheel@v3.4 env: CIBW_PLATFORM: android with: diff --git a/.github/workflows/upstream.yml b/.github/workflows/upstream.yml index 051cffc04a..51354d68c3 100644 --- a/.github/workflows/upstream.yml +++ b/.github/workflows/upstream.yml @@ -36,7 +36,7 @@ jobs: run: sudo apt-get install libboost-dev - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.1 + uses: jwlawson/actions-setup-cmake@v2.2 - name: Run pip installs run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5ee5846b5f..637dc3f94f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,14 +25,14 @@ repos: # Clang format the codebase automatically - repo: https://github.com/pre-commit/mirrors-clang-format - rev: "v22.1.0" + rev: "v22.1.2" hooks: - id: clang-format types_or: [c++, c, cuda] # Ruff, the Python auto-correcting linter/formatter written in Rust - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.4 + rev: v0.15.9 hooks: - id: ruff-check args: ["--fix", "--show-fixes"] @@ -40,7 +40,7 @@ repos: # Check static types with mypy - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.19.1" + rev: "v1.20.0" hooks: - id: mypy args: [] @@ -112,7 +112,7 @@ repos: # Use tools/codespell_ignore_lines_from_errors.py # to rebuild .codespell-ignore-lines - repo: https://github.com/codespell-project/codespell - rev: "v2.4.1" + rev: "v2.4.2" hooks: - id: codespell exclude: "(.supp|^pyproject.toml)$" @@ -122,7 +122,7 @@ repos: # Use mirror because pre-commit autoupdate confuses tags in the upstream repo. # See https://github.com/crate-ci/typos/issues/390 - repo: https://github.com/adhtruong/mirrors-typos - rev: "v1.44.0" + rev: "v1.45.0" hooks: - id: typos args: [] @@ -151,7 +151,7 @@ repos: # Check schemas on some of our YAML files - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.37.0 + rev: 0.37.1 hooks: - id: check-readthedocs - id: check-github-workflows diff --git a/docs/changelog.md b/docs/changelog.md index f993034f14..ecff9705e2 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -13,6 +13,34 @@ Changes will be added here periodically from the "Suggested changelog entry" block in pull request descriptions. +## Version 3.0.4 (April 18, 2026) + +Bug fixes: + +- Fixed test builds with installed Eigen 5 by improving `Eigen3` CMake package detection. + [#6036](https://github.com/pybind/pybind11/pull/6036) + +- Fixed move semantics of `scoped_ostream_redirect` to preserve buffered output and avoid crashes when moved redirects restore stream buffers. + [#6033](https://github.com/pybind/pybind11/pull/6033) + +- Fixed `py::dynamic_attr()` traversal on Python 3.13+ to correctly propagate `PyObject_VisitManagedDict()` results. + [#6032](https://github.com/pybind/pybind11/pull/6032) + +- Fixed `std::shared_ptr` fallback casting to avoid unnecessary copy-constructor instantiation in `reference_internal` paths. + [#6028](https://github.com/pybind/pybind11/pull/6028) + +CI: + +- Updated `setup-uv` to the maintained GitHub Action tag scheme. + [#6035](https://github.com/pybind/pybind11/pull/6035) + +- Updated pre-commit hooks. + [#6029](https://github.com/pybind/pybind11/pull/6029) + +- Updated GitHub Actions dependencies, including `actions-setup-cmake` and `cibuildwheel`. + [#6027](https://github.com/pybind/pybind11/pull/6027) + + ## Version 3.0.3 (March 31, 2026) Bug fixes: diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index f07abfea3d..9ebabcebb4 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1026,7 +1026,7 @@ struct copyable_holder_caster< } if (parent) { - return type_caster_base::cast( + return type_caster_generic::cast_non_owning( srcs, return_value_policy::reference_internal, parent); } diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 4b7422eee2..8b9d0b8e9d 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -578,7 +578,10 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) { /// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`. extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) { #if PY_VERSION_HEX >= 0x030D0000 - PyObject_VisitManagedDict(self, visit, arg); + int ret = PyObject_VisitManagedDict(self, visit, arg); + if (ret) { + return ret; + } #else PyObject *&dict = *_PyObject_GetDictPtr(self); Py_VISIT(dict); diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 7158923084..38cb01d70a 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -19,7 +19,7 @@ /* -- start version constants -- */ #define PYBIND11_VERSION_MAJOR 3 #define PYBIND11_VERSION_MINOR 0 -#define PYBIND11_VERSION_MICRO 3 +#define PYBIND11_VERSION_MICRO 4 // ALPHA = 0xA, BETA = 0xB, GAMMA = 0xC (release candidate), FINAL = 0xF (stable release) // - The release level is set to "alpha" for development versions. // Use 0xA0 (LEVEL=0xA, SERIAL=0) for development versions. @@ -27,7 +27,7 @@ #define PYBIND11_VERSION_RELEASE_LEVEL PY_RELEASE_LEVEL_FINAL #define PYBIND11_VERSION_RELEASE_SERIAL 0 // String version of (micro, release level, release serial), e.g.: 0a0, 0b1, 0rc1, 0 -#define PYBIND11_VERSION_PATCH 3 +#define PYBIND11_VERSION_PATCH 4 /* -- end version constants -- */ #if !defined(Py_PACK_FULL_VERSION) diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index b0c59e1138..8fbf700e12 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -1004,6 +1004,18 @@ class type_caster_generic { return cast(srcs, policy, parent, copy_constructor, move_constructor, existing_holder); } + static handle cast_non_owning(const cast_sources &srcs, + return_value_policy policy, + handle parent, + const void *existing_holder = nullptr) { + // Reference-like policies alias an existing C++ object instead of creating + // a new one, so copy/move constructor callbacks must remain null here. + assert(policy == return_value_policy::reference + || policy == return_value_policy::reference_internal + || policy == return_value_policy::automatic_reference); + return cast(srcs, policy, parent, nullptr, nullptr, existing_holder); + } + PYBIND11_NOINLINE static handle cast(const cast_sources &srcs, return_value_policy policy, handle parent, diff --git a/include/pybind11/iostream.h b/include/pybind11/iostream.h index 44261e881e..df7fa3c381 100644 --- a/include/pybind11/iostream.h +++ b/include/pybind11/iostream.h @@ -131,7 +131,22 @@ class pythonbuf : public std::streambuf { setp(d_buffer.get(), d_buffer.get() + buf_size - 1); } - pythonbuf(pythonbuf &&) = default; + pythonbuf(pythonbuf &&other) noexcept + : buf_size(other.buf_size), d_buffer(std::move(other.d_buffer)), + pywrite(std::move(other.pywrite)), pyflush(std::move(other.pyflush)) { + const auto pending = (other.pbase() != nullptr && other.pptr() != nullptr) + ? static_cast(other.pptr() - other.pbase()) + : 0; + if (d_buffer != nullptr) { + // Rebuild the put area from the transferred storage. + setp(d_buffer.get(), d_buffer.get() + buf_size - 1); + pbump(pending); + } else { + setp(nullptr, nullptr); + } + // Prevent the moved-from destructor from flushing through moved-out handles. + other.setp(nullptr, nullptr); + } /// Sync before destroy ~pythonbuf() override { _sync(); } @@ -169,6 +184,7 @@ class scoped_ostream_redirect { std::streambuf *old; std::ostream &costream; detail::pythonbuf buffer; + bool active = true; public: explicit scoped_ostream_redirect(std::ostream &costream = std::cout, @@ -178,10 +194,22 @@ class scoped_ostream_redirect { old = costream.rdbuf(&buffer); } - ~scoped_ostream_redirect() { costream.rdbuf(old); } + ~scoped_ostream_redirect() { + if (active) { + costream.rdbuf(old); + } + } scoped_ostream_redirect(const scoped_ostream_redirect &) = delete; - scoped_ostream_redirect(scoped_ostream_redirect &&other) = default; + // NOLINTNEXTLINE(performance-noexcept-move-constructor) + scoped_ostream_redirect(scoped_ostream_redirect &&other) + : old(other.old), costream(other.costream), buffer(std::move(other.buffer)), + active(other.active) { + if (active) { + costream.rdbuf(&buffer); // Re-point stream to our buffer + other.active = false; + } + } scoped_ostream_redirect &operator=(const scoped_ostream_redirect &) = delete; scoped_ostream_redirect &operator=(scoped_ostream_redirect &&) = delete; }; diff --git a/pyproject.toml b/pyproject.toml index 6a300985ad..03214c6f4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -202,6 +202,10 @@ fo = "fo" quater = "quater" optin = "optin" othr = "othr" +# NumPy uses "writeable" in public API names and flags. +writeable = "writeable" +Writeable = "Writeable" +WRITEABLE = "WRITEABLE" #[tool.typos.type.cpp.extend-words] setp = "setp" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e87b1e93b3..fc08a9056f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -167,6 +167,7 @@ set(PYBIND11_TEST_FILES test_operator_overloading test_pickling test_potentially_slicing_weak_ptr + test_pytorch_shared_ptr_cast_regression test_python_multiple_inheritance test_pytypes test_scoped_critical_section @@ -299,10 +300,17 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) else() find_package(Eigen3 3.2.7 QUIET CONFIG) + if(NOT Eigen3_FOUND) + find_package(Eigen3 5 QUIET CONFIG) + endif() + set(EIGEN3_FOUND ${Eigen3_FOUND}) + set(EIGEN3_VERSION ${Eigen3_VERSION}) if(NOT EIGEN3_FOUND) # Couldn't load via target, so fall back to allowing module mode finding, which will pick up # tools/FindEigen3.cmake + # This MODULE-mode fallback is for older Eigen 3 setups; Eigen 5 is expected to be found + # via the CONFIG-mode probes above. find_package(Eigen3 3.2.7 QUIET) endif() endif() diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 2030cd6715..84efb800db 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -104,6 +104,10 @@ TEST_SUBMODULE(class_, m) { ~NoConstructorNew() { print_destroyed(this); } }; + struct DynamicAttr { + DynamicAttr() = default; + }; + py::class_(m, "NoConstructor") .def_static("new_instance", &NoConstructor::new_instance, "Return an instance"); @@ -112,6 +116,8 @@ TEST_SUBMODULE(class_, m) { .def_static("__new__", [](const py::object &) { return NoConstructorNew::new_instance(); }); + py::class_(m, "DynamicAttr", py::dynamic_attr()).def(py::init<>()); + // test_pass_unique_ptr struct ToBeHeldByUniquePtr {}; py::class_>(m, "ToBeHeldByUniquePtr") diff --git a/tests/test_class.py b/tests/test_class.py index fae6a31899..201c7e339e 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -1,5 +1,6 @@ from __future__ import annotations +import gc import sys from unittest import mock @@ -18,6 +19,13 @@ def refcount_immortal(ob: object) -> int: return sys.getrefcount(ob) +MANAGED_DICT_GET_REFERRERS_SUPPORTED = ( + env.CPYTHON + and sys.version_info >= (3, 13, 13) + and (sys.version_info < (3, 14) or sys.version_info >= (3, 14, 4)) +) + + def test_obj_class_name(): expected_name = "UserType" if env.PYPY else "pybind11_tests.UserType" assert m.obj_class_name(UserType(1)) == expected_name @@ -45,6 +53,16 @@ def test_instance(msg): assert cstats.alive() == 0 +@pytest.mark.skipif( + not MANAGED_DICT_GET_REFERRERS_SUPPORTED, + reason="Requires CPython 3.13.13+ or 3.14.4+ managed dict traversal support", +) +def test_get_referrers(): + instance = m.DynamicAttr() + instance.a = "test" + assert instance in gc.get_referrers(instance.__dict__) + + def test_instance_new(): instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__) diff --git a/tests/test_class_sh_property.py b/tests/test_class_sh_property.py index 4a7b77c69a..b8a5933e5f 100644 --- a/tests/test_class_sh_property.py +++ b/tests/test_class_sh_property.py @@ -204,3 +204,15 @@ def test_non_smart_holder_member_type_with_smart_holder_owner_aliases_member(): legacy = obj.legacy legacy.value = 13 assert obj.legacy.value == 13 + + +def test_non_smart_holder_member_type_with_smart_holder_owner_aliases_member_multiple_reads(): + obj = m.ShWithSimpleStructMember() + + a = obj.legacy + b = obj.legacy + + a.value = 13 + + assert b.value == 13 + assert obj.legacy.value == 13 diff --git a/tests/test_iostream.cpp b/tests/test_iostream.cpp index 421eaa2dd8..7484e734be 100644 --- a/tests/test_iostream.cpp +++ b/tests/test_iostream.cpp @@ -123,4 +123,40 @@ TEST_SUBMODULE(iostream, m) { .def("stop", &TestThread::stop) .def("join", &TestThread::join) .def("sleep", &TestThread::sleep); + + m.def("move_redirect_output", [](const std::string &msg_before, const std::string &msg_after) { + py::scoped_ostream_redirect redir1(std::cout, py::module_::import("sys").attr("stdout")); + std::cout << msg_before << std::flush; + py::scoped_ostream_redirect redir2(std::move(redir1)); + std::cout << msg_after << std::flush; + }); + + m.def("move_redirect_output_unflushed", + [](const std::string &msg_before, const std::string &msg_after) { + py::scoped_ostream_redirect redir1(std::cout, + py::module_::import("sys").attr("stdout")); + std::cout << msg_before; + py::scoped_ostream_redirect redir2(std::move(redir1)); + std::cout << msg_after << std::flush; + }); + + // Redirect a stream whose original rdbuf is nullptr, then move the redirect. + // Verifies that nullptr is correctly restored (not confused with a moved-from sentinel). + m.def("move_redirect_null_rdbuf", [](const std::string &msg) { + std::ostream os(nullptr); + py::scoped_ostream_redirect redir1(os, py::module_::import("sys").attr("stdout")); + os << msg << std::flush; + py::scoped_ostream_redirect redir2(std::move(redir1)); + os << msg << std::flush; + // After redir2 goes out of scope, os.rdbuf() should be restored to nullptr. + }); + + m.def("get_null_rdbuf_restored", [](const std::string &msg) -> bool { + std::ostream os(nullptr); + { + py::scoped_ostream_redirect redir(os, py::module_::import("sys").attr("stdout")); + os << msg << std::flush; + } + return os.rdbuf() == nullptr; + }); } diff --git a/tests/test_iostream.py b/tests/test_iostream.py index 791b9e0483..857e0b5f73 100644 --- a/tests/test_iostream.py +++ b/tests/test_iostream.py @@ -284,6 +284,31 @@ def test_redirect_both(capfd): assert stream2.getvalue() == msg2 +def test_move_redirect(capsys): + m.move_redirect_output("before_move", "after_move") + stdout, stderr = capsys.readouterr() + assert stdout == "before_moveafter_move" + assert not stderr + + +def test_move_redirect_unflushed(capsys): + m.move_redirect_output_unflushed("before_move", "after_move") + stdout, stderr = capsys.readouterr() + assert stdout == "before_moveafter_move" + assert not stderr + + +def test_move_redirect_null_rdbuf(capsys): + m.move_redirect_null_rdbuf("hello") + stdout, stderr = capsys.readouterr() + assert stdout == "hellohello" + assert not stderr + + +def test_null_rdbuf_restored(): + assert m.get_null_rdbuf_restored("test") + + @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_threading(): with m.ostream_redirect(stdout=True, stderr=False): diff --git a/tests/test_pytorch_shared_ptr_cast_regression.cpp b/tests/test_pytorch_shared_ptr_cast_regression.cpp new file mode 100644 index 0000000000..1425962242 --- /dev/null +++ b/tests/test_pytorch_shared_ptr_cast_regression.cpp @@ -0,0 +1,62 @@ +#include "pybind11_tests.h" + +#include +#include + +#if defined(__clang__) +# if __has_warning("-Wdeprecated-copy-with-user-provided-dtor") +# pragma clang diagnostic error "-Wdeprecated-copy-with-user-provided-dtor" +# endif +# if __has_warning("-Wdeprecated-copy-with-dtor") +# pragma clang diagnostic error "-Wdeprecated-copy-with-dtor" +# endif +#endif + +namespace test_pytorch_regressions { + +// Directly extracted from PyTorch patterns that regressed in CI. +struct TracingState : std::enable_shared_from_this { + TracingState() = default; + ~TracingState() = default; + int value = 0; +}; + +const std::shared_ptr &get_tracing_state() { + static std::shared_ptr state = std::make_shared(); + return state; +} + +struct InterfaceType { + ~InterfaceType() = default; + int value = 0; +}; +using InterfaceTypePtr = std::shared_ptr; + +struct CompilationUnit { + InterfaceTypePtr iface = std::make_shared(); + + InterfaceTypePtr get_interface(const std::string &) const { return iface; } +}; + +} // namespace test_pytorch_regressions + +TEST_SUBMODULE(pybind11_pytorch_regressions, m) { + using namespace test_pytorch_regressions; + + py::class_>(m, "TracingState") + .def(py::init<>()) + .def_readwrite("value", &TracingState::value); + + m.def("_get_tracing_state", []() { return get_tracing_state(); }); + + py::class_(m, "InterfaceType") + .def(py::init<>()) + .def_readwrite("value", &InterfaceType::value); + + py::class_>(m, "CompilationUnit") + .def(py::init<>()) + .def("get_interface", + [](const std::shared_ptr &self, const std::string &name) { + return self->get_interface(name); + }); +} diff --git a/tests/test_pytorch_shared_ptr_cast_regression.py b/tests/test_pytorch_shared_ptr_cast_regression.py new file mode 100644 index 0000000000..b7c393b325 --- /dev/null +++ b/tests/test_pytorch_shared_ptr_cast_regression.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from pybind11_tests import pybind11_pytorch_regressions as m + + +def test_pytorch_like_get_tracing_state_aliases_singleton_shared_ptr(): + a = m._get_tracing_state() + b = m._get_tracing_state() + + a.value = 17 + + assert b.value == 17 + assert m._get_tracing_state().value == 17 + + +def test_pytorch_like_compilation_unit_get_interface_aliases_member_shared_ptr(): + cu = m.CompilationUnit() + + a = cu.get_interface("iface") + b = cu.get_interface("iface") + + a.value = 23 + + assert b.value == 23 + assert cu.get_interface("iface").value == 23