Skip to content

Commit 63b7283

Browse files
committed
Fix class name in alias constructors
For classes with an alias, the type shows up in the constructor as "handle" rather than the actual type, because py::init<> uses a `handle` first argument in such a case because it needs to make a run-time decision as to whether to construct a Base or Alias type. This fixes the signature by replacing the first argument type the scope (i.e. module) name when the first argument of an instance method is set to `handle`. This also ends up fixing the `self` argument of non-constructor methods taking `self` as a handle, which seems less important, but still desirable.
1 parent fb50ce1 commit 63b7283

File tree

3 files changed

+26
-3
lines changed

3 files changed

+26
-3
lines changed

include/pybind11/pybind11.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,16 @@ class cpp_function : public function {
230230
const std::type_info *t = types[type_index++];
231231
if (!t)
232232
pybind11_fail("Internal error while parsing type signature (1)");
233-
if (auto tinfo = detail::get_type_info(*t)) {
233+
234+
detail::type_info *tinfo = nullptr;
235+
// If this is the first arg of a constructor (i.e. self) and is `handle`,
236+
// replace it with the class (handle is used internally when dealing with
237+
// aliases and factory initializers).
238+
if (arg_index == 0 && rec->is_method && t == &typeid(handle) && PyType_Check(rec->scope.ptr()))
239+
tinfo = detail::get_type_info((PyTypeObject *) rec->scope.ptr());
240+
if (!tinfo)
241+
tinfo = detail::get_type_info(*t);
242+
if (tinfo) {
234243
#if defined(PYPY_VERSION)
235244
signature += handle((PyObject *) tinfo->type)
236245
.attr("__module__")

tests/test_alias_initialization.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ test_initializer alias_initialization([](py::module &m) {
3232

3333
py::class_<A, PyA>(m, "A")
3434
.def(py::init<>())
35-
.def("f", &A::f);
35+
.def("f", &A::f)
36+
.def("self_as_handle", [](py::handle) {});
3637

3738
m.def("call_f", call_f);
3839

tests/test_alias_initialization.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

33

4-
def test_alias_delay_initialization1(capture):
4+
def test_alias_delay_initialization1(capture, msg):
55
"""
66
A only initializes its trampoline class when we inherit from it; if we just
77
create and use an A instance directly, the trampoline initialization is
@@ -37,6 +37,19 @@ def f(self):
3737
PyA.~PyA()
3838
"""
3939

40+
# Check doc string and failure for the proper type (not just "handle") in the output
41+
assert A.__init__.__doc__ == "__init__(self: pybind11_tests.A) -> None\n"
42+
with pytest.raises(TypeError) as excinfo:
43+
A(1, 2)
44+
assert msg(excinfo.value) == """
45+
__init__(): incompatible constructor arguments. The following argument types are supported:
46+
1. m.A()
47+
48+
Invoked with: 1, 2
49+
"""
50+
51+
assert A.self_as_handle.__doc__ == "self_as_handle(self: pybind11_tests.A) -> None\n"
52+
4053

4154
def test_alias_delay_initialization2(capture):
4255
"""A2, unlike the above, is configured to always initialize the alias; while

0 commit comments

Comments
 (0)