Skip to content

Commit 71aea49

Browse files
Check scope's __dict__ instead of using hasattr when registering classes and exceptions (#2335)
* Check scope's __dict__ instead of using hasattr when registering classes and exceptions, to allow registering the same name in a derived class scope * Extend test_base_and_derived_nested_scope test * Add tests on error being thrown registering duplicate classes * Circumvent bug with combination of test_class.py::test_register_duplicate_class and test_factory_constructors.py::test_init_factory_alias
1 parent deba040 commit 71aea49

File tree

3 files changed

+77
-2
lines changed

3 files changed

+77
-2
lines changed

include/pybind11/pybind11.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,7 @@ class generic_type : public object {
10121012
PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check)
10131013
protected:
10141014
void initialize(const type_record &rec) {
1015-
if (rec.scope && hasattr(rec.scope, rec.name))
1015+
if (rec.scope && hasattr(rec.scope, "__dict__") && rec.scope.attr("__dict__").contains(rec.name))
10161016
pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec.name) +
10171017
"\": an object with that name is already defined");
10181018

@@ -1930,7 +1930,7 @@ class exception : public object {
19301930
std::string full_name = scope.attr("__name__").cast<std::string>() +
19311931
std::string(".") + name;
19321932
m_ptr = PyErr_NewException(const_cast<char *>(full_name.c_str()), base.ptr(), NULL);
1933-
if (hasattr(scope, name))
1933+
if (hasattr(scope, "__dict__") && scope.attr("__dict__").contains(name))
19341934
pybind11_fail("Error during initialization: multiple incompatible "
19351935
"definitions with name \"" + std::string(name) + "\"");
19361936
scope.attr(name) = *this;

tests/test_class.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ TEST_SUBMODULE(class_, m) {
406406
struct IsNonFinalFinal {};
407407
py::class_<IsNonFinalFinal>(m, "IsNonFinalFinal", py::is_final());
408408

409+
// test_exception_rvalue_abort
409410
struct PyPrintDestructor {
410411
PyPrintDestructor() = default;
411412
~PyPrintDestructor() {
@@ -417,6 +418,7 @@ TEST_SUBMODULE(class_, m) {
417418
.def(py::init<>())
418419
.def("throw_something", &PyPrintDestructor::throw_something);
419420

421+
// test_multiple_instances_with_same_pointer
420422
struct SamePointer {};
421423
static SamePointer samePointer;
422424
py::class_<SamePointer, std::unique_ptr<SamePointer, py::nodelete>>(m, "SamePointer")
@@ -426,6 +428,44 @@ TEST_SUBMODULE(class_, m) {
426428
struct Empty {};
427429
py::class_<Empty>(m, "Empty")
428430
.def(py::init<>());
431+
432+
// test_base_and_derived_nested_scope
433+
struct BaseWithNested {
434+
struct Nested {};
435+
};
436+
437+
struct DerivedWithNested : BaseWithNested {
438+
struct Nested {};
439+
};
440+
441+
py::class_<BaseWithNested> baseWithNested_class(m, "BaseWithNested");
442+
py::class_<DerivedWithNested, BaseWithNested> derivedWithNested_class(m, "DerivedWithNested");
443+
py::class_<BaseWithNested::Nested>(baseWithNested_class, "Nested")
444+
.def_static("get_name", []() { return "BaseWithNested::Nested"; });
445+
py::class_<DerivedWithNested::Nested>(derivedWithNested_class, "Nested")
446+
.def_static("get_name", []() { return "DerivedWithNested::Nested"; });
447+
448+
// test_register_duplicate_class
449+
struct Duplicate {};
450+
struct OtherDuplicate {};
451+
struct DuplicateNested {};
452+
struct OtherDuplicateNested {};
453+
m.def("register_duplicate_class_name", [](py::module_ m) {
454+
py::class_<Duplicate>(m, "Duplicate");
455+
py::class_<OtherDuplicate>(m, "Duplicate");
456+
});
457+
m.def("register_duplicate_class_type", [](py::module_ m) {
458+
py::class_<OtherDuplicate>(m, "OtherDuplicate");
459+
py::class_<OtherDuplicate>(m, "YetAnotherDuplicate");
460+
});
461+
m.def("register_duplicate_nested_class_name", [](py::object gt) {
462+
py::class_<DuplicateNested>(gt, "DuplicateNested");
463+
py::class_<OtherDuplicateNested>(gt, "DuplicateNested");
464+
});
465+
m.def("register_duplicate_nested_class_type", [](py::object gt) {
466+
py::class_<OtherDuplicateNested>(gt, "OtherDuplicateNested");
467+
py::class_<OtherDuplicateNested>(gt, "YetAnotherDuplicateNested");
468+
});
429469
}
430470

431471
template <int N> class BreaksBase { public:

tests/test_class.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,3 +383,38 @@ def test_multiple_instances_with_same_pointer(capture):
383383
# No assert: if this does not trigger the error
384384
# pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
385385
# and just completes without crashing, we're good.
386+
387+
388+
# https://github.com/pybind/pybind11/issues/1624
389+
def test_base_and_derived_nested_scope():
390+
assert issubclass(m.DerivedWithNested, m.BaseWithNested)
391+
assert m.BaseWithNested.Nested != m.DerivedWithNested.Nested
392+
assert m.BaseWithNested.Nested.get_name() == "BaseWithNested::Nested"
393+
assert m.DerivedWithNested.Nested.get_name() == "DerivedWithNested::Nested"
394+
395+
396+
@pytest.mark.skip("See https://github.com/pybind/pybind11/pull/2564")
397+
def test_register_duplicate_class():
398+
import types
399+
module_scope = types.ModuleType("module_scope")
400+
with pytest.raises(RuntimeError) as exc_info:
401+
m.register_duplicate_class_name(module_scope)
402+
expected = ('generic_type: cannot initialize type "Duplicate": '
403+
'an object with that name is already defined')
404+
assert str(exc_info.value) == expected
405+
with pytest.raises(RuntimeError) as exc_info:
406+
m.register_duplicate_class_type(module_scope)
407+
expected = 'generic_type: type "YetAnotherDuplicate" is already registered!'
408+
assert str(exc_info.value) == expected
409+
410+
class ClassScope:
411+
pass
412+
with pytest.raises(RuntimeError) as exc_info:
413+
m.register_duplicate_nested_class_name(ClassScope)
414+
expected = ('generic_type: cannot initialize type "DuplicateNested": '
415+
'an object with that name is already defined')
416+
assert str(exc_info.value) == expected
417+
with pytest.raises(RuntimeError) as exc_info:
418+
m.register_duplicate_nested_class_type(ClassScope)
419+
expected = 'generic_type: type "YetAnotherDuplicateNested" is already registered!'
420+
assert str(exc_info.value) == expected

0 commit comments

Comments
 (0)