From a8d9a0a2e5360c7e3956297bc4a4357b681fcd62 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Aug 2026 22:51:35 -0400 Subject: [PATCH 1/2] feat: move type_caster_base.h non-template definitions to type_caster_base-inl.h Moves the free functions (type-info lookup and registration, instance layout, isinstance_generic, cpp_conduit_method, type_info_description), the loader_life_support members (the function-local thread_local stack stays per-module in both modes), and the two heavy type_caster_generic members (the type_info constructor and the main cast overload). Templates, including load_impl<>, stay in the header. Assisted-by: ClaudeCode:claude-fable-5 --- CMakeLists.txt | 1 + .../pybind11/detail/type_caster_base-inl.h | 548 ++++++++++++++++++ include/pybind11/detail/type_caster_base.h | 538 ++--------------- src/pybind11_combined.cpp | 1 + src/type_caster_base.cpp | 10 + tests/extra_python_package/test_files.py | 2 + 6 files changed, 605 insertions(+), 495 deletions(-) create mode 100644 include/pybind11/detail/type_caster_base-inl.h create mode 100644 src/type_caster_base.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3157a57e1d..35b0035267 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,7 @@ set(PYBIND11_HEADERS include/pybind11/detail/native_enum_data.h include/pybind11/detail/pybind11_namespace_macros.h include/pybind11/detail/struct_smart_holder.h + include/pybind11/detail/type_caster_base-inl.h include/pybind11/detail/type_caster_base.h include/pybind11/detail/typeid.h include/pybind11/detail/using_smart_holder.h diff --git a/include/pybind11/detail/type_caster_base-inl.h b/include/pybind11/detail/type_caster_base-inl.h new file mode 100644 index 0000000000..eb85c25f39 --- /dev/null +++ b/include/pybind11/detail/type_caster_base-inl.h @@ -0,0 +1,548 @@ +/* + pybind11/detail/type_caster_base-inl.h: Out-of-line definitions for type_caster_base.h + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +// Every function defined here must start with PYBIND11_INLINE (or +// PYBIND11_NOINLINE_ATTR PYBIND11_INLINE). In the default header-only mode this file is +// included at the bottom of type_caster_base.h; when PYBIND11_PRECOMPILED is defined it +// is only compiled into the pybind11 static library (see src/). + +#pragma once + +#include "type_caster_base.h" + +#include +#include +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +PYBIND11_INLINE loader_life_support *&loader_life_support::tls_current_frame() { + static thread_local loader_life_support *frame_ptr = nullptr; + return frame_ptr; +} + +PYBIND11_INLINE loader_life_support::loader_life_support() { + auto &frame = tls_current_frame(); + parent = frame; + frame = this; +} + +PYBIND11_INLINE loader_life_support::~loader_life_support() { + auto &frame = tls_current_frame(); + if (frame != this) { + pybind11_fail("loader_life_support: internal error"); + } + frame = parent; + for (auto *item : keep_alive) { + Py_DECREF(item); + } +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE bool loader_life_support::try_add_patient(handle h) { + loader_life_support *frame = tls_current_frame(); + if (!frame) { + return false; + } + if (frame->keep_alive.insert(h.ptr()).second) { + Py_INCREF(h.ptr()); + } + return true; +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void loader_life_support::add_patient(handle h) { + if (!try_add_patient(h)) { + // NOTE: It would be nice to include the stack frames here, as this indicates + // use of pybind11::cast<> outside the normal call framework, finding such + // a location is challenging. Developers could consider printing out + // stack frame addresses here using something like __builtin_frame_address(0) + throw cast_error("When called outside a bound function, py::cast() cannot " + "do Python -> C++ conversions which require the creation " + "of temporary values"); + } +} + +// Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762. +PYBIND11_INLINE void all_type_info_add_base_most_derived_first(std::vector &bases, + type_info *addl_base) { + for (auto it = bases.begin(); it != bases.end(); it++) { + type_info *existing_base = *it; + if (PyType_IsSubtype(addl_base->type, existing_base->type) != 0) { + bases.insert(it, addl_base); + return; + } + } + bases.push_back(addl_base); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void all_type_info_populate(PyTypeObject *t, std::vector &bases) { + assert(bases.empty()); + std::vector check; + for (handle parent : reinterpret_borrow(t->tp_bases)) { + check.push_back(reinterpret_cast(parent.ptr())); + } + auto const &type_dict = get_internals().registered_types_py; + for (size_t i = 0; i < check.size(); i++) { + auto *type = check[i]; + // Ignore Python2 old-style class super type: + if (!PyType_Check((PyObject *) type)) { + continue; + } + + // Check `type` in the current set of registered python types: + auto it = type_dict.find(type); + if (it != type_dict.end()) { + // We found a cache entry for it, so it's either pybind-registered or has pre-computed + // pybind bases, but we have to make sure we haven't already seen the type(s) before: + // we want to follow Python/virtual C++ rules that there should only be one instance of + // a common base. + for (auto *tinfo : it->second) { + // NB: Could use a second set here, rather than doing a linear search, but since + // having a large number of immediate pybind11-registered types seems fairly + // unlikely, that probably isn't worthwhile. + bool found = false; + for (auto *known : bases) { + if (known == tinfo) { + found = true; + break; + } + } + if (!found) { + all_type_info_add_base_most_derived_first(bases, tinfo); + } + } + } else if (type->tp_bases) { + // It's some python type, so keep follow its bases classes to look for one or more + // registered types + if (i + 1 == check.size()) { + // When we're at the end, we can pop off the current element to avoid growing + // `check` when adding just one base (which is typical--i.e. when there is no + // multiple inheritance) + check.pop_back(); + i--; + } + for (handle parent : reinterpret_borrow(type->tp_bases)) { + check.push_back(reinterpret_cast(parent.ptr())); + } + } + } +} + +PYBIND11_INLINE const std::vector &all_type_info(PyTypeObject *type) { + return all_type_info_get_cache(type).first->second; +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE detail::type_info *get_type_info(PyTypeObject *type) { + const auto &bases = all_type_info(type); + if (bases.empty()) { + return nullptr; + } + if (bases.size() > 1) { + pybind11_fail( + "pybind11::detail::get_type_info: type has multiple pybind11-registered bases"); + } + return bases.front(); +} + +PYBIND11_INLINE detail::type_info *get_local_type_info_lock_held(const std::type_info &tp) { + const auto &locals = get_local_internals().registered_types_cpp; + auto it = locals.find(&tp); + if (it != locals.end()) { + return it->second; + } + return nullptr; +} + +PYBIND11_INLINE detail::type_info *get_local_type_info(const std::type_info &tp) { + // NB: internals and local_internals share a single mutex + PYBIND11_LOCK_INTERNALS(get_internals()); + return get_local_type_info_lock_held(tp); +} + +PYBIND11_INLINE detail::type_info *get_global_type_info_lock_held(const std::type_info &tp) { + // This is a two-level lookup. Hopefully we find the type info in + // registered_types_cpp_fast, but if not we try + // registered_types_cpp and fill registered_types_cpp_fast for + // next time. + detail::type_info *type_info = nullptr; + auto &internals = get_internals(); +#if PYBIND11_INTERNALS_VERSION >= 12 + auto &fast_types = internals.registered_types_cpp_fast; +#endif + auto &types = internals.registered_types_cpp; +#if PYBIND11_INTERNALS_VERSION >= 12 + auto fast_it = fast_types.find(&tp); + if (fast_it != fast_types.end()) { +# ifndef NDEBUG + auto types_it = types.find(std::type_index(tp)); + assert(types_it != types.end()); + assert(types_it->second == fast_it->second); +# endif + return fast_it->second; + } +#endif // PYBIND11_INTERNALS_VERSION >= 12 + + auto it = types.find(std::type_index(tp)); + if (it != types.end()) { +#if PYBIND11_INTERNALS_VERSION >= 12 + // We found the type in the slow map but not the fast one, so + // some other DSO added it (otherwise it would be in the fast + // map under &tp) and therefore we must be an alias. Record + // that. + it->second->alias_chain.push_front(&tp); + fast_types.emplace(&tp, it->second); +#endif + type_info = it->second; + } + return type_info; +} + +PYBIND11_INLINE detail::type_info *get_global_type_info(const std::type_info &tp) { + PYBIND11_LOCK_INTERNALS(get_internals()); + return get_global_type_info_lock_held(tp); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE detail::type_info *get_type_info(const std::type_info &tp, + bool throw_if_missing) { + PYBIND11_LOCK_INTERNALS(get_internals()); + if (auto *ltype = get_local_type_info_lock_held(tp)) { + return ltype; + } + if (auto *gtype = get_global_type_info_lock_held(tp)) { + return gtype; + } + + if (throw_if_missing) { + std::string tname = tp.name(); + detail::clean_type_id(tname); + pybind11_fail("pybind11::detail::get_type_info: unable to find type info for \"" + + std::move(tname) + '"'); + } + return nullptr; +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle get_type_handle(const std::type_info &tp, bool throw_if_missing) { + detail::type_info *type_info = get_type_info(tp, throw_if_missing); + return handle(type_info ? (reinterpret_cast(type_info->type)) : nullptr); +} + +PYBIND11_INLINE bool try_incref(PyObject *obj) { + // Tries to increment the reference count of an object if it's not zero. +#if defined(Py_GIL_DISABLED) && PY_VERSION_HEX >= 0x030E00A4 + return PyUnstable_TryIncRef(obj); +#elif defined(Py_GIL_DISABLED) + // See + // https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761 + uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local); + local += 1; + if (local == 0) { + // immortal + return true; + } + if (_Py_IsOwnedByCurrentThread(obj)) { + _Py_atomic_store_uint32_relaxed(&obj->ob_ref_local, local); +# ifdef Py_REF_DEBUG + _Py_INCREF_IncRefTotal(); +# endif + return true; + } + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared); + for (;;) { + // If the shared refcount is zero and the object is either merged + // or may not have weak references, then we cannot incref it. + if (shared == 0 || shared == _Py_REF_MERGED) { + return false; + } + + if (_Py_atomic_compare_exchange_ssize( + &obj->ob_ref_shared, &shared, shared + (1 << _Py_REF_SHARED_SHIFT))) { +# ifdef Py_REF_DEBUG + _Py_INCREF_IncRefTotal(); +# endif + return true; + } + } +#else + assert(Py_REFCNT(obj) > 0); + Py_INCREF(obj); + return true; +#endif +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle find_registered_python_instance(void *src, + const detail::type_info *tinfo) { + return with_instance_map(src, [&](instance_map &instances) { + auto it_instances = instances.equal_range(src); + for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) { + for (auto *instance_type : detail::all_type_info(Py_TYPE(it_i->second))) { + if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) { + auto *wrapper = reinterpret_cast(it_i->second); + if (try_incref(wrapper)) { + return handle(wrapper); + } + } + } + } + return handle(); + }); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE value_and_holder +instance::get_value_and_holder(const type_info *find_type /*= nullptr default in common.h*/, + bool throw_if_missing /*= true in common.h*/) { + // Optimize common case: + if (!find_type || Py_TYPE(this) == find_type->type) { + return value_and_holder(this, find_type, 0, 0); + } + + detail::values_and_holders vhs(this); + auto it = vhs.find(find_type); + if (it != vhs.end()) { + return *it; + } + + if (!throw_if_missing) { + return value_and_holder(); + } + +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) + pybind11_fail("pybind11::detail::instance::get_value_and_holder: `" + + get_fully_qualified_tp_name(find_type->type) + + "' is not a pybind11 base of the given `" + + get_fully_qualified_tp_name(Py_TYPE(this)) + "' instance"); +#else + pybind11_fail( + "pybind11::detail::instance::get_value_and_holder: " + "type is not a pybind11 base of the given instance " + "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for type details)"); +#endif +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void instance::allocate_layout() { + const auto &tinfo = all_type_info(Py_TYPE(this)); + + const size_t n_types = tinfo.size(); + + if (n_types == 0) { + pybind11_fail( + "instance allocation failed: new instance has no pybind11-registered base types"); + } + + simple_layout + = n_types == 1 && tinfo.front()->holder_size_in_ptrs <= instance_simple_holder_in_ptrs(); + + // Simple path: no python-side multiple inheritance, and a small-enough holder + if (simple_layout) { + simple_value_holder[0] = nullptr; + simple_holder_constructed = false; + simple_instance_registered = false; + } else { // multiple base types or a too-large holder + // Allocate space to hold: [v1*][h1][v2*][h2]...[bb...] where [vN*] is a value pointer, + // [hN] is the (uninitialized) holder instance for value N, and [bb...] is a set of bool + // values that tracks whether each associated holder has been initialized. Each [block] is + // padded, if necessary, to an integer multiple of sizeof(void *). + size_t space = 0; + for (auto *t : tinfo) { + space += 1; // value pointer + space += t->holder_size_in_ptrs; // holder instance + } + size_t flags_at = space; + space += size_in_ptrs(n_types); // status bytes (holder_constructed and + // instance_registered) + + // Allocate space for flags, values, and holders, and initialize it to 0 (flags and values, + // in particular, need to be 0). Use Python's memory allocation + // functions: Python is using pymalloc, which is designed to be + // efficient for small allocations like the one we're doing here; + // for larger allocations they are just wrappers around malloc. + // TODO: is this still true for pure Python 3.6? + nonsimple.values_and_holders = static_cast(PyMem_Calloc(space, sizeof(void *))); + if (!nonsimple.values_and_holders) { + throw std::bad_alloc(); + } + nonsimple.status + = reinterpret_cast(&nonsimple.values_and_holders[flags_at]); + } + owned = true; +} + +// NOLINTNEXTLINE(readability-make-member-function-const) +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void instance::deallocate_layout() { + if (!simple_layout) { + PyMem_Free(reinterpret_cast(nonsimple.values_and_holders)); + } +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE bool isinstance_generic(handle obj, const std::type_info &tp) { + handle type = detail::get_type_handle(tp, false); + if (!type) { + return false; + } + return isinstance(obj, type); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle get_object_handle(const void *ptr, const detail::type_info *type) { + return with_instance_map(ptr, [&](instance_map &instances) { + auto range = instances.equal_range(ptr); + for (auto it = range.first; it != range.second; ++it) { + for (const auto &vh : values_and_holders(it->second)) { + if (vh.type == type) { + return handle(reinterpret_cast(it->second)); + } + } + } + return handle(); + }); +} + +PYBIND11_INLINE object cpp_conduit_method(handle self, + const bytes &pybind11_platform_abi_id, + const capsule &cpp_type_info_capsule, + const bytes &pointer_kind) { +#ifdef PYBIND11_HAS_STRING_VIEW + using cpp_str = std::string_view; +#else + using cpp_str = std::string; +#endif + if (cpp_str(pybind11_platform_abi_id) != PYBIND11_PLATFORM_ABI_ID) { + return none(); + } + if (std::strcmp(cpp_type_info_capsule.name(), typeid(std::type_info).name()) != 0) { + return none(); + } + if (cpp_str(pointer_kind) != "raw_pointer_ephemeral") { + throw std::runtime_error("Invalid pointer_kind: \"" + std::string(pointer_kind) + "\""); + } + const auto *cpp_type_info = cpp_type_info_capsule.get_pointer(); + type_caster_generic caster(*cpp_type_info); + if (!caster.load(self, false)) { + return none(); + } + return capsule(caster.value, cpp_type_info->name()); +} + +PYBIND11_INLINE std::string quote_cpp_type_name(const std::string &cpp_type_name) { + return cpp_type_name; // No-op for now. See PR #4888 +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE std::string type_info_description(const std::type_info &ti) { + if (auto *type_data = get_type_info(ti)) { + handle th(reinterpret_cast(type_data->type)); + return th.attr("__module__").cast() + '.' + + th.attr("__qualname__").cast(); + } + return quote_cpp_type_name(clean_type_id(ti.name())); +} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE +type_caster_generic::type_caster_generic(const std::type_info &type_info) + : typeinfo(get_type_info(type_info)), cpptype(&type_info) {} + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle +type_caster_generic::cast(const cast_sources &srcs, + return_value_policy policy, + handle parent, + void *(*copy_constructor)(const void *), + void *(*move_constructor)(const void *), + const void *existing_holder) { + if (!srcs.result.tinfo) { + // No pybind11 type info. Raise an exception. + std::string tname = srcs.downcast.cpptype ? srcs.downcast.cpptype->name() + : srcs.original.cpptype ? srcs.original.cpptype->name() + : ""; + detail::clean_type_id(tname); + std::string msg = "Unregistered type : " + tname; + set_error(PyExc_TypeError, msg.c_str()); + return handle(); + } + + void *src = const_cast(srcs.result.cppobj); + if (src == nullptr) { + return none().release(); + } + const type_info *tinfo = srcs.result.tinfo; + + if (handle registered_inst = find_registered_python_instance(src, tinfo)) { + return registered_inst; + } + + auto inst = reinterpret_steal(make_new_instance(tinfo->type)); + auto *wrapper = reinterpret_cast(inst.ptr()); + wrapper->owned = false; + void *&valueptr = values_and_holders(wrapper).begin()->value_ptr(); + + switch (policy) { + case return_value_policy::automatic: + case return_value_policy::take_ownership: + valueptr = src; + wrapper->owned = true; + break; + + case return_value_policy::automatic_reference: + case return_value_policy::reference: + valueptr = src; + wrapper->owned = false; + break; + + case return_value_policy::copy: + if (copy_constructor) { + valueptr = copy_constructor(src); + } else { +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) + std::string type_name(tinfo->cpptype->name()); + detail::clean_type_id(type_name); + throw cast_error("return_value_policy = copy, but type " + type_name + + " is non-copyable!"); +#else + throw cast_error("return_value_policy = copy, but type is " + "non-copyable! (#define PYBIND11_DETAILED_ERROR_MESSAGES or " + "compile in debug mode for details)"); +#endif + } + wrapper->owned = true; + break; + + case return_value_policy::move: + if (move_constructor) { + valueptr = move_constructor(src); + } else if (copy_constructor) { + valueptr = copy_constructor(src); + } else { +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) + std::string type_name(tinfo->cpptype->name()); + detail::clean_type_id(type_name); + throw cast_error("return_value_policy = move, but type " + type_name + + " is neither movable nor copyable!"); +#else + throw cast_error("return_value_policy = move, but type is neither " + "movable nor copyable! " + "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in " + "debug mode for details)"); +#endif + } + wrapper->owned = true; + break; + + case return_value_policy::reference_internal: + valueptr = src; + wrapper->owned = false; + keep_alive_impl(inst, parent); + break; + + default: + throw cast_error("unhandled return_value_policy: should not happen!"); + } + + tinfo->init_instance(wrapper, existing_holder); + + return inst.release(); +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 7fd9c449eb..4163673df7 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -52,138 +52,45 @@ class loader_life_support { // saves a significant cost per function call spent in // loader_life_support destruction. // Note for future C++17 simplification: - // inline static thread_local loader_life_support *tls_current_frame = nullptr; - static loader_life_support *&tls_current_frame() { - static thread_local loader_life_support *frame_ptr = nullptr; - return frame_ptr; - } + // inline static thread_local loader_life_support *frame_ptr = nullptr; + // (Keeping the function-local static: its address is the per-module frame stack, so + // it must live in whatever binary each module links, header-only or precompiled.) + static loader_life_support *&tls_current_frame(); loader_life_support *parent = nullptr; std::unordered_set keep_alive; public: /// A new patient frame is created when a function is entered - loader_life_support() { - auto &frame = tls_current_frame(); - parent = frame; - frame = this; - } + loader_life_support(); /// ... and destroyed after it returns - ~loader_life_support() { - auto &frame = tls_current_frame(); - if (frame != this) { - pybind11_fail("loader_life_support: internal error"); - } - frame = parent; - for (auto *item : keep_alive) { - Py_DECREF(item); - } - } + ~loader_life_support(); /// Keep `h` alive until the current patient frame is destroyed, if there is one. /// Returns false when called outside a bound function (no frame). Use this, rather /// than `add_patient`, when failing to register is acceptable because the caller /// owns the source's lifetime outside the call framework (e.g. a view that points /// into an existing Python object, as opposed to a freshly created temporary). - PYBIND11_NOINLINE static bool try_add_patient(handle h) { - loader_life_support *frame = tls_current_frame(); - if (!frame) { - return false; - } - if (frame->keep_alive.insert(h.ptr()).second) { - Py_INCREF(h.ptr()); - } - return true; - } + static bool try_add_patient(handle h); /// This can only be used inside a pybind11-bound function, either by `argument_loader` /// at argument preparation time or by `py::cast()` at execution time. - PYBIND11_NOINLINE static void add_patient(handle h) { - if (!try_add_patient(h)) { - // NOTE: It would be nice to include the stack frames here, as this indicates - // use of pybind11::cast<> outside the normal call framework, finding such - // a location is challenging. Developers could consider printing out - // stack frame addresses here using something like __builtin_frame_address(0) - throw cast_error("When called outside a bound function, py::cast() cannot " - "do Python -> C++ conversions which require the creation " - "of temporary values"); - } - } + static void add_patient(handle h); }; // Gets the cache entry for the given type, creating it if necessary. The return value is the pair // returned by emplace, i.e. an iterator for the entry and a bool set to `true` if the entry was // just created. -inline std::pair +std::pair all_type_info_get_cache(PyTypeObject *type); // Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762. -inline void all_type_info_add_base_most_derived_first(std::vector &bases, - type_info *addl_base) { - for (auto it = bases.begin(); it != bases.end(); it++) { - type_info *existing_base = *it; - if (PyType_IsSubtype(addl_base->type, existing_base->type) != 0) { - bases.insert(it, addl_base); - return; - } - } - bases.push_back(addl_base); -} +void all_type_info_add_base_most_derived_first(std::vector &bases, + type_info *addl_base); // Populates a just-created cache entry. -PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector &bases) { - assert(bases.empty()); - std::vector check; - for (handle parent : reinterpret_borrow(t->tp_bases)) { - check.push_back(reinterpret_cast(parent.ptr())); - } - auto const &type_dict = get_internals().registered_types_py; - for (size_t i = 0; i < check.size(); i++) { - auto *type = check[i]; - // Ignore Python2 old-style class super type: - if (!PyType_Check((PyObject *) type)) { - continue; - } - - // Check `type` in the current set of registered python types: - auto it = type_dict.find(type); - if (it != type_dict.end()) { - // We found a cache entry for it, so it's either pybind-registered or has pre-computed - // pybind bases, but we have to make sure we haven't already seen the type(s) before: - // we want to follow Python/virtual C++ rules that there should only be one instance of - // a common base. - for (auto *tinfo : it->second) { - // NB: Could use a second set here, rather than doing a linear search, but since - // having a large number of immediate pybind11-registered types seems fairly - // unlikely, that probably isn't worthwhile. - bool found = false; - for (auto *known : bases) { - if (known == tinfo) { - found = true; - break; - } - } - if (!found) { - all_type_info_add_base_most_derived_first(bases, tinfo); - } - } - } else if (type->tp_bases) { - // It's some python type, so keep follow its bases classes to look for one or more - // registered types - if (i + 1 == check.size()) { - // When we're at the end, we can pop off the current element to avoid growing - // `check` when adding just one base (which is typical--i.e. when there is no - // multiple inheritance) - check.pop_back(); - i--; - } - for (handle parent : reinterpret_borrow(type->tp_bases)) { - check.push_back(reinterpret_cast(parent.ptr())); - } - } - } -} +void all_type_info_populate(PyTypeObject *t, std::vector &bases); /** * Extracts vector of type_info pointers of pybind-registered roots of the given Python type. Will @@ -195,172 +102,33 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector &all_type_info(PyTypeObject *type) { - return all_type_info_get_cache(type).first->second; -} +const std::vector &all_type_info(PyTypeObject *type); /** * Gets a single pybind11 type info for a python type. Returns nullptr if neither the type nor any * ancestors are pybind11-registered. Throws an exception if there are multiple bases--use * `all_type_info` instead if you want to support multiple bases. */ -PYBIND11_NOINLINE detail::type_info *get_type_info(PyTypeObject *type) { - const auto &bases = all_type_info(type); - if (bases.empty()) { - return nullptr; - } - if (bases.size() > 1) { - pybind11_fail( - "pybind11::detail::get_type_info: type has multiple pybind11-registered bases"); - } - return bases.front(); -} +detail::type_info *get_type_info(PyTypeObject *type); -inline detail::type_info *get_local_type_info_lock_held(const std::type_info &tp) { - const auto &locals = get_local_internals().registered_types_cpp; - auto it = locals.find(&tp); - if (it != locals.end()) { - return it->second; - } - return nullptr; -} +detail::type_info *get_local_type_info_lock_held(const std::type_info &tp); -inline detail::type_info *get_local_type_info(const std::type_info &tp) { - // NB: internals and local_internals share a single mutex - PYBIND11_LOCK_INTERNALS(get_internals()); - return get_local_type_info_lock_held(tp); -} +detail::type_info *get_local_type_info(const std::type_info &tp); -inline detail::type_info *get_global_type_info_lock_held(const std::type_info &tp) { - // This is a two-level lookup. Hopefully we find the type info in - // registered_types_cpp_fast, but if not we try - // registered_types_cpp and fill registered_types_cpp_fast for - // next time. - detail::type_info *type_info = nullptr; - auto &internals = get_internals(); -#if PYBIND11_INTERNALS_VERSION >= 12 - auto &fast_types = internals.registered_types_cpp_fast; -#endif - auto &types = internals.registered_types_cpp; -#if PYBIND11_INTERNALS_VERSION >= 12 - auto fast_it = fast_types.find(&tp); - if (fast_it != fast_types.end()) { -# ifndef NDEBUG - auto types_it = types.find(std::type_index(tp)); - assert(types_it != types.end()); - assert(types_it->second == fast_it->second); -# endif - return fast_it->second; - } -#endif // PYBIND11_INTERNALS_VERSION >= 12 - - auto it = types.find(std::type_index(tp)); - if (it != types.end()) { -#if PYBIND11_INTERNALS_VERSION >= 12 - // We found the type in the slow map but not the fast one, so - // some other DSO added it (otherwise it would be in the fast - // map under &tp) and therefore we must be an alias. Record - // that. - it->second->alias_chain.push_front(&tp); - fast_types.emplace(&tp, it->second); -#endif - type_info = it->second; - } - return type_info; -} +detail::type_info *get_global_type_info_lock_held(const std::type_info &tp); -inline detail::type_info *get_global_type_info(const std::type_info &tp) { - PYBIND11_LOCK_INTERNALS(get_internals()); - return get_global_type_info_lock_held(tp); -} +detail::type_info *get_global_type_info(const std::type_info &tp); /// Return the type info for a given C++ type; on lookup failure can either throw or return /// nullptr. -PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_info &tp, - bool throw_if_missing = false) { - PYBIND11_LOCK_INTERNALS(get_internals()); - if (auto *ltype = get_local_type_info_lock_held(tp)) { - return ltype; - } - if (auto *gtype = get_global_type_info_lock_held(tp)) { - return gtype; - } +detail::type_info *get_type_info(const std::type_info &tp, bool throw_if_missing = false); - if (throw_if_missing) { - std::string tname = tp.name(); - detail::clean_type_id(tname); - pybind11_fail("pybind11::detail::get_type_info: unable to find type info for \"" - + std::move(tname) + '"'); - } - return nullptr; -} +handle get_type_handle(const std::type_info &tp, bool throw_if_missing); -PYBIND11_NOINLINE handle get_type_handle(const std::type_info &tp, bool throw_if_missing) { - detail::type_info *type_info = get_type_info(tp, throw_if_missing); - return handle(type_info ? (reinterpret_cast(type_info->type)) : nullptr); -} - -inline bool try_incref(PyObject *obj) { - // Tries to increment the reference count of an object if it's not zero. -#if defined(Py_GIL_DISABLED) && PY_VERSION_HEX >= 0x030E00A4 - return PyUnstable_TryIncRef(obj); -#elif defined(Py_GIL_DISABLED) - // See - // https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761 - uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local); - local += 1; - if (local == 0) { - // immortal - return true; - } - if (_Py_IsOwnedByCurrentThread(obj)) { - _Py_atomic_store_uint32_relaxed(&obj->ob_ref_local, local); -# ifdef Py_REF_DEBUG - _Py_INCREF_IncRefTotal(); -# endif - return true; - } - Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared); - for (;;) { - // If the shared refcount is zero and the object is either merged - // or may not have weak references, then we cannot incref it. - if (shared == 0 || shared == _Py_REF_MERGED) { - return false; - } - - if (_Py_atomic_compare_exchange_ssize( - &obj->ob_ref_shared, &shared, shared + (1 << _Py_REF_SHARED_SHIFT))) { -# ifdef Py_REF_DEBUG - _Py_INCREF_IncRefTotal(); -# endif - return true; - } - } -#else - assert(Py_REFCNT(obj) > 0); - Py_INCREF(obj); - return true; -#endif -} +bool try_incref(PyObject *obj); // Searches the inheritance graph for a registered Python instance, using all_type_info(). -PYBIND11_NOINLINE handle find_registered_python_instance(void *src, - const detail::type_info *tinfo) { - return with_instance_map(src, [&](instance_map &instances) { - auto it_instances = instances.equal_range(src); - for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) { - for (auto *instance_type : detail::all_type_info(Py_TYPE(it_i->second))) { - if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) { - auto *wrapper = reinterpret_cast(it_i->second); - if (try_incref(wrapper)) { - return handle(wrapper); - } - } - } - } - return handle(); - }); -} +handle find_registered_python_instance(void *src, const detail::type_info *tinfo); // Container for accessing and iterating over an instance's values/holders struct values_and_holders { @@ -448,113 +216,12 @@ struct values_and_holders { * The returned object should be short-lived: in particular, it must not outlive the called-upon * instance. */ -PYBIND11_NOINLINE value_and_holder -instance::get_value_and_holder(const type_info *find_type /*= nullptr default in common.h*/, - bool throw_if_missing /*= true in common.h*/) { - // Optimize common case: - if (!find_type || Py_TYPE(this) == find_type->type) { - return value_and_holder(this, find_type, 0, 0); - } +// (get_value_and_holder, allocate_layout, and deallocate_layout are declared inside +// struct instance in detail/common.h; definitions are in type_caster_base-inl.h.) - detail::values_and_holders vhs(this); - auto it = vhs.find(find_type); - if (it != vhs.end()) { - return *it; - } - - if (!throw_if_missing) { - return value_and_holder(); - } - -#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) - pybind11_fail("pybind11::detail::instance::get_value_and_holder: `" - + get_fully_qualified_tp_name(find_type->type) - + "' is not a pybind11 base of the given `" - + get_fully_qualified_tp_name(Py_TYPE(this)) + "' instance"); -#else - pybind11_fail( - "pybind11::detail::instance::get_value_and_holder: " - "type is not a pybind11 base of the given instance " - "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for type details)"); -#endif -} - -PYBIND11_NOINLINE void instance::allocate_layout() { - const auto &tinfo = all_type_info(Py_TYPE(this)); - - const size_t n_types = tinfo.size(); - - if (n_types == 0) { - pybind11_fail( - "instance allocation failed: new instance has no pybind11-registered base types"); - } - - simple_layout - = n_types == 1 && tinfo.front()->holder_size_in_ptrs <= instance_simple_holder_in_ptrs(); - - // Simple path: no python-side multiple inheritance, and a small-enough holder - if (simple_layout) { - simple_value_holder[0] = nullptr; - simple_holder_constructed = false; - simple_instance_registered = false; - } else { // multiple base types or a too-large holder - // Allocate space to hold: [v1*][h1][v2*][h2]...[bb...] where [vN*] is a value pointer, - // [hN] is the (uninitialized) holder instance for value N, and [bb...] is a set of bool - // values that tracks whether each associated holder has been initialized. Each [block] is - // padded, if necessary, to an integer multiple of sizeof(void *). - size_t space = 0; - for (auto *t : tinfo) { - space += 1; // value pointer - space += t->holder_size_in_ptrs; // holder instance - } - size_t flags_at = space; - space += size_in_ptrs(n_types); // status bytes (holder_constructed and - // instance_registered) - - // Allocate space for flags, values, and holders, and initialize it to 0 (flags and values, - // in particular, need to be 0). Use Python's memory allocation - // functions: Python is using pymalloc, which is designed to be - // efficient for small allocations like the one we're doing here; - // for larger allocations they are just wrappers around malloc. - // TODO: is this still true for pure Python 3.6? - nonsimple.values_and_holders = static_cast(PyMem_Calloc(space, sizeof(void *))); - if (!nonsimple.values_and_holders) { - throw std::bad_alloc(); - } - nonsimple.status - = reinterpret_cast(&nonsimple.values_and_holders[flags_at]); - } - owned = true; -} +bool isinstance_generic(handle obj, const std::type_info &tp); -// NOLINTNEXTLINE(readability-make-member-function-const) -PYBIND11_NOINLINE void instance::deallocate_layout() { - if (!simple_layout) { - PyMem_Free(reinterpret_cast(nonsimple.values_and_holders)); - } -} - -PYBIND11_NOINLINE bool isinstance_generic(handle obj, const std::type_info &tp) { - handle type = detail::get_type_handle(tp, false); - if (!type) { - return false; - } - return isinstance(obj, type); -} - -PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_info *type) { - return with_instance_map(ptr, [&](instance_map &instances) { - auto range = instances.equal_range(ptr); - for (auto it = range.first; it != range.second; ++it) { - for (const auto &vh : values_and_holders(it->second)) { - if (vh.type == type) { - return handle(reinterpret_cast(it->second)); - } - } - } - return handle(); - }); -} +handle get_object_handle(const void *ptr, const detail::type_info *type); // Information about how type_caster_generic::cast() can obtain its source object struct cast_sources { @@ -996,8 +663,7 @@ PYBIND11_NAMESPACE_END(smart_holder_type_caster_support) class type_caster_generic { public: - PYBIND11_NOINLINE explicit type_caster_generic(const std::type_info &type_info) - : typeinfo(get_type_info(type_info)), cpptype(&type_info) {} + explicit type_caster_generic(const std::type_info &type_info); explicit type_caster_generic(const type_info *typeinfo) : typeinfo(typeinfo), cpptype(typeinfo ? typeinfo->cpptype : nullptr) {} @@ -1027,104 +693,12 @@ class type_caster_generic { return cast(srcs, policy, parent, nullptr, nullptr, existing_holder); } - PYBIND11_NOINLINE static handle cast(const cast_sources &srcs, - return_value_policy policy, - handle parent, - void *(*copy_constructor)(const void *), - void *(*move_constructor)(const void *), - const void *existing_holder = nullptr) { - if (!srcs.result.tinfo) { - // No pybind11 type info. Raise an exception. - std::string tname = srcs.downcast.cpptype ? srcs.downcast.cpptype->name() - : srcs.original.cpptype ? srcs.original.cpptype->name() - : ""; - detail::clean_type_id(tname); - std::string msg = "Unregistered type : " + tname; - set_error(PyExc_TypeError, msg.c_str()); - return handle(); - } - - void *src = const_cast(srcs.result.cppobj); - if (src == nullptr) { - return none().release(); - } - const type_info *tinfo = srcs.result.tinfo; - - if (handle registered_inst = find_registered_python_instance(src, tinfo)) { - return registered_inst; - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *wrapper = reinterpret_cast(inst.ptr()); - wrapper->owned = false; - void *&valueptr = values_and_holders(wrapper).begin()->value_ptr(); - - switch (policy) { - case return_value_policy::automatic: - case return_value_policy::take_ownership: - valueptr = src; - wrapper->owned = true; - break; - - case return_value_policy::automatic_reference: - case return_value_policy::reference: - valueptr = src; - wrapper->owned = false; - break; - - case return_value_policy::copy: - if (copy_constructor) { - valueptr = copy_constructor(src); - } else { -#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) - std::string type_name(tinfo->cpptype->name()); - detail::clean_type_id(type_name); - throw cast_error("return_value_policy = copy, but type " + type_name - + " is non-copyable!"); -#else - throw cast_error("return_value_policy = copy, but type is " - "non-copyable! (#define PYBIND11_DETAILED_ERROR_MESSAGES or " - "compile in debug mode for details)"); -#endif - } - wrapper->owned = true; - break; - - case return_value_policy::move: - if (move_constructor) { - valueptr = move_constructor(src); - } else if (copy_constructor) { - valueptr = copy_constructor(src); - } else { -#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) - std::string type_name(tinfo->cpptype->name()); - detail::clean_type_id(type_name); - throw cast_error("return_value_policy = move, but type " + type_name - + " is neither movable nor copyable!"); -#else - throw cast_error("return_value_policy = move, but type is neither " - "movable nor copyable! " - "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in " - "debug mode for details)"); -#endif - } - wrapper->owned = true; - break; - - case return_value_policy::reference_internal: - valueptr = src; - wrapper->owned = false; - keep_alive_impl(inst, parent); - break; - - default: - throw cast_error("unhandled return_value_policy: should not happen!"); - } - - tinfo->init_instance(wrapper, existing_holder); - - return inst.release(); - } + static handle cast(const cast_sources &srcs, + return_value_policy policy, + handle parent, + void *(*copy_constructor)(const void *), + void *(*move_constructor)(const void *), + const void *existing_holder = nullptr); // Base methods for generic caster; there are overridden in copyable_holder_caster void load_value(value_and_holder &&v_h) { @@ -1326,31 +900,10 @@ class type_caster_generic { void *value = nullptr; }; -inline object cpp_conduit_method(handle self, - const bytes &pybind11_platform_abi_id, - const capsule &cpp_type_info_capsule, - const bytes &pointer_kind) { -#ifdef PYBIND11_HAS_STRING_VIEW - using cpp_str = std::string_view; -#else - using cpp_str = std::string; -#endif - if (cpp_str(pybind11_platform_abi_id) != PYBIND11_PLATFORM_ABI_ID) { - return none(); - } - if (std::strcmp(cpp_type_info_capsule.name(), typeid(std::type_info).name()) != 0) { - return none(); - } - if (cpp_str(pointer_kind) != "raw_pointer_ephemeral") { - throw std::runtime_error("Invalid pointer_kind: \"" + std::string(pointer_kind) + "\""); - } - const auto *cpp_type_info = cpp_type_info_capsule.get_pointer(); - type_caster_generic caster(*cpp_type_info); - if (!caster.load(self, false)) { - return none(); - } - return capsule(caster.value, cpp_type_info->name()); -} +object cpp_conduit_method(handle self, + const bytes &pybind11_platform_abi_id, + const capsule &cpp_type_info_capsule, + const bytes &pointer_kind); /** * Determine suitable casting operator for pointer-or-lvalue-casting type casters. The type caster @@ -1717,18 +1270,13 @@ class type_caster_base : public type_caster_generic { static Constructor make_move_constructor(...) { return nullptr; } }; -inline std::string quote_cpp_type_name(const std::string &cpp_type_name) { - return cpp_type_name; // No-op for now. See PR #4888 -} +std::string quote_cpp_type_name(const std::string &cpp_type_name); -PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) { - if (auto *type_data = get_type_info(ti)) { - handle th(reinterpret_cast(type_data->type)); - return th.attr("__module__").cast() + '.' - + th.attr("__qualname__").cast(); - } - return quote_cpp_type_name(clean_type_id(ti.name())); -} +std::string type_info_description(const std::type_info &ti); PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#ifndef PYBIND11_PRECOMPILED +# include "type_caster_base-inl.h" // IWYU pragma: export +#endif diff --git a/src/pybind11_combined.cpp b/src/pybind11_combined.cpp index 2bfe127509..718ddc8291 100644 --- a/src/pybind11_combined.cpp +++ b/src/pybind11_combined.cpp @@ -13,5 +13,6 @@ #include #include +#include #include #include diff --git a/src/type_caster_base.cpp b/src/type_caster_base.cpp new file mode 100644 index 0000000000..f9119044dc --- /dev/null +++ b/src/type_caster_base.cpp @@ -0,0 +1,10 @@ +// Copyright (c) 2025 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#if !defined(PYBIND11_PRECOMPILED) +# error "pybind11 library sources must be compiled with PYBIND11_PRECOMPILED defined." +#endif + +#include +#include diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 840df80ac2..79af6f679e 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -97,6 +97,7 @@ "include/pybind11/detail/native_enum_data.h", "include/pybind11/detail/pybind11_namespace_macros.h", "include/pybind11/detail/struct_smart_holder.h", + "include/pybind11/detail/type_caster_base-inl.h", "include/pybind11/detail/type_caster_base.h", "include/pybind11/detail/typeid.h", "include/pybind11/detail/using_smart_holder.h", @@ -132,6 +133,7 @@ sdist_src_files = { "src/class.cpp", "src/internals.cpp", + "src/type_caster_base.cpp", "src/pybind11_combined.cpp", "src/pytypes.cpp", } From 635a9dbb2383885d5e6d14b4c359db48c2a86809 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 03:08:25 +0000 Subject: [PATCH 2/2] style: pre-commit fixes --- .../pybind11/detail/type_caster_base-inl.h | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/include/pybind11/detail/type_caster_base-inl.h b/include/pybind11/detail/type_caster_base-inl.h index eb85c25f39..4a9ba0d822 100644 --- a/include/pybind11/detail/type_caster_base-inl.h +++ b/include/pybind11/detail/type_caster_base-inl.h @@ -70,7 +70,7 @@ PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void loader_life_support::add_patient(han // Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762. PYBIND11_INLINE void all_type_info_add_base_most_derived_first(std::vector &bases, - type_info *addl_base) { + type_info *addl_base) { for (auto it = bases.begin(); it != bases.end(); it++) { type_info *existing_base = *it; if (PyType_IsSubtype(addl_base->type, existing_base->type) != 0) { @@ -81,7 +81,8 @@ PYBIND11_INLINE void all_type_info_add_base_most_derived_first(std::vector &bases) { +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void +all_type_info_populate(PyTypeObject *t, std::vector &bases) { assert(bases.empty()); std::vector check; for (handle parent : reinterpret_borrow(t->tp_bases)) { @@ -209,7 +210,7 @@ PYBIND11_INLINE detail::type_info *get_global_type_info(const std::type_info &tp } PYBIND11_NOINLINE_ATTR PYBIND11_INLINE detail::type_info *get_type_info(const std::type_info &tp, - bool throw_if_missing) { + bool throw_if_missing) { PYBIND11_LOCK_INTERNALS(get_internals()); if (auto *ltype = get_local_type_info_lock_held(tp)) { return ltype; @@ -227,7 +228,8 @@ PYBIND11_NOINLINE_ATTR PYBIND11_INLINE detail::type_info *get_type_info(const st return nullptr; } -PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle get_type_handle(const std::type_info &tp, bool throw_if_missing) { +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle get_type_handle(const std::type_info &tp, + bool throw_if_missing) { detail::type_info *type_info = get_type_info(tp, throw_if_missing); return handle(type_info ? (reinterpret_cast(type_info->type)) : nullptr); } @@ -275,8 +277,8 @@ PYBIND11_INLINE bool try_incref(PyObject *obj) { #endif } -PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle find_registered_python_instance(void *src, - const detail::type_info *tinfo) { +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle +find_registered_python_instance(void *src, const detail::type_info *tinfo) { return with_instance_map(src, [&](instance_map &instances) { auto it_instances = instances.equal_range(src); for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) { @@ -379,7 +381,8 @@ PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void instance::deallocate_layout() { } } -PYBIND11_NOINLINE_ATTR PYBIND11_INLINE bool isinstance_generic(handle obj, const std::type_info &tp) { +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE bool isinstance_generic(handle obj, + const std::type_info &tp) { handle type = detail::get_type_handle(tp, false); if (!type) { return false; @@ -387,7 +390,8 @@ PYBIND11_NOINLINE_ATTR PYBIND11_INLINE bool isinstance_generic(handle obj, const return isinstance(obj, type); } -PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle get_object_handle(const void *ptr, const detail::type_info *type) { +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle get_object_handle(const void *ptr, + const detail::type_info *type) { return with_instance_map(ptr, [&](instance_map &instances) { auto range = instances.equal_range(ptr); for (auto it = range.first; it != range.second; ++it) { @@ -402,9 +406,9 @@ PYBIND11_NOINLINE_ATTR PYBIND11_INLINE handle get_object_handle(const void *ptr, } PYBIND11_INLINE object cpp_conduit_method(handle self, - const bytes &pybind11_platform_abi_id, - const capsule &cpp_type_info_capsule, - const bytes &pointer_kind) { + const bytes &pybind11_platform_abi_id, + const capsule &cpp_type_info_capsule, + const bytes &pointer_kind) { #ifdef PYBIND11_HAS_STRING_VIEW using cpp_str = std::string_view; #else @@ -431,7 +435,8 @@ PYBIND11_INLINE std::string quote_cpp_type_name(const std::string &cpp_type_name return cpp_type_name; // No-op for now. See PR #4888 } -PYBIND11_NOINLINE_ATTR PYBIND11_INLINE std::string type_info_description(const std::type_info &ti) { +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE std::string +type_info_description(const std::type_info &ti) { if (auto *type_data = get_type_info(ti)) { handle th(reinterpret_cast(type_data->type)); return th.attr("__module__").cast() + '.'