Add is_final to disallow inheritance from Python
- Not currently supported on PyPy
diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst
index ae5907d..20760b7 100644
--- a/docs/advanced/classes.rst
+++ b/docs/advanced/classes.rst
@@ -1042,6 +1042,32 @@
``.def("foo", static_cast<int (A::*)() const>(&Publicist::foo));``
where ``int (A::*)() const`` is the type of ``A::foo``.
+Binding final classes
+=====================
+
+Some classes may not be appropriate to inherit from. In C++11, classes can
+use the ``final`` specifier to ensure that a class cannot be inherited from.
+The ``py::is_final`` attribute can be used to ensure that Python classes
+cannot inherit from a specified type. The underlying C++ type does not need
+to be declared final.
+
+.. code-block:: cpp
+
+ class IsFinal final {};
+
+ py::class_<IsFinal>(m, "IsFinal", py::is_final());
+
+When you try to inherit from such a class in Python, you will now get this
+error:
+
+.. code-block:: pycon
+
+ >>> class PyFinalChild(IsFinal):
+ ... pass
+ TypeError: type 'IsFinal' is not an acceptable base type
+
+.. note:: This attribute is currently ignored on PyPy
+
Custom automatic downcasters
============================
diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h
index 6962d6f..744284a 100644
--- a/include/pybind11/attr.h
+++ b/include/pybind11/attr.h
@@ -23,6 +23,9 @@
/// Annotation for operators
struct is_operator { };
+/// Annotation for classes that cannot be subclassed
+struct is_final { };
+
/// Annotation for parent scope
struct scope { handle value; scope(const handle &s) : value(s) { } };
@@ -201,7 +204,7 @@
struct type_record {
PYBIND11_NOINLINE type_record()
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false),
- default_holder(true), module_local(false) { }
+ default_holder(true), module_local(false), is_final(false) { }
/// Handle to the parent scope
handle scope;
@@ -254,6 +257,9 @@
/// Is the class definition local to the module shared object?
bool module_local : 1;
+ /// Is the class inheritable from python classes?
+ bool is_final : 1;
+
PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *)) {
auto base_info = detail::get_type_info(base, false);
if (!base_info) {
@@ -417,6 +423,11 @@
};
template <>
+struct process_attribute<is_final> : process_attribute_default<is_final> {
+ static void init(const is_final &, type_record *r) { r->is_final = true; }
+};
+
+template <>
struct process_attribute<buffer_protocol> : process_attribute_default<buffer_protocol> {
static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; }
};
diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h
index edfa7de..a05edeb 100644
--- a/include/pybind11/detail/class.h
+++ b/include/pybind11/detail/class.h
@@ -604,10 +604,12 @@
#endif
/* Flags */
- type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
+ type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE;
#if PY_MAJOR_VERSION < 3
type->tp_flags |= Py_TPFLAGS_CHECKTYPES;
#endif
+ if (!rec.is_final)
+ type->tp_flags |= Py_TPFLAGS_BASETYPE;
if (rec.dynamic_attr)
enable_dynamic_attributes(heap_type);
diff --git a/tests/test_class.cpp b/tests/test_class.cpp
index 499d0cc..128bc39 100644
--- a/tests/test_class.cpp
+++ b/tests/test_class.cpp
@@ -367,6 +367,14 @@
.def(py::init<>())
.def("ptr", &Aligned::ptr);
#endif
+
+ // test_final
+ struct IsFinal final {};
+ py::class_<IsFinal>(m, "IsFinal", py::is_final());
+
+ // test_non_final_final
+ struct IsNonFinalFinal {};
+ py::class_<IsNonFinalFinal>(m, "IsNonFinalFinal", py::is_final());
}
template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; };
diff --git a/tests/test_class.py b/tests/test_class.py
index ed63ca8..e58fef6 100644
--- a/tests/test_class.py
+++ b/tests/test_class.py
@@ -279,3 +279,21 @@
if hasattr(m, "Aligned"):
p = m.Aligned().ptr()
assert p % 1024 == 0
+
+
+# https://bitbucket.org/pypy/pypy/issues/2742
+@pytest.unsupported_on_pypy
+def test_final():
+ with pytest.raises(TypeError) as exc_info:
+ class PyFinalChild(m.IsFinal):
+ pass
+ assert str(exc_info.value).endswith("is not an acceptable base type")
+
+
+# https://bitbucket.org/pypy/pypy/issues/2742
+@pytest.unsupported_on_pypy
+def test_non_final_final():
+ with pytest.raises(TypeError) as exc_info:
+ class PyNonFinalFinalChild(m.IsNonFinalFinal):
+ pass
+ assert str(exc_info.value).endswith("is not an acceptable base type")