Commit fefd5c56 authored by Xavier Thompson's avatar Xavier Thompson

Allow more than one extension type base class when the others all have empty layout

parent 66a4a62e
......@@ -120,7 +120,6 @@ def NAME(self, ARGDECLS):
self.synthesized = set()
self.nesting_stack = []
self.module_scope = node.scope
self.cimport_cython = True
self.visitchildren(node)
self.inject_cypclass_wrappers(node)
return node
......@@ -183,24 +182,10 @@ def NAME(self, ARGDECLS):
nested_name = "_".join(nested_names)
cclass_name = self.create_unique_name("%s_cyp_cclass_wrapper" % nested_name)
pyclass_name = self.create_unique_name("%s_cyp_pyclass_wrapper" % nested_name)
self.type_to_names[node.entry.type] = qualified_name, cclass_name, pyclass_name
self.type_to_names[node.entry.type] = qualified_name, cclass_name
def inject_cypclass_wrappers(self, module_node):
if self.cimport_cython:
# cimport cython to access the @cython.binding decorator
# use a unique name for "cimport cython as <name>" if necessary
as_name = self.create_unique_name("cython")
self.cython_as_name = as_name
cimport_stmt = Nodes.CImportStatNode(
module_node.pos,
module_name=EncodedString("cython"),
as_name=None if as_name == "cython" else as_name,
is_absolute=True
)
self.wrappers.append(cimport_stmt)
for collected in self.collected_cypclasses:
self.synthesize_wrappers(collected)
......@@ -224,7 +209,7 @@ def NAME(self, ARGDECLS):
self.base_type_to_deferred[wrapped_base_type].append(lambda: self.synthesize_wrappers(node))
return
qualified_name, cclass_name, pyclass_name = self.type_to_names[node_type]
qualified_name, cclass_name = self.type_to_names[node_type]
cclass = self.synthesize_wrapper_cclass(node, cclass_name, qualified_name)
......@@ -234,25 +219,7 @@ def NAME(self, ARGDECLS):
# forward declare the cclass wrapper
cclass.declare(self.module_scope)
pyclass = self.synthesize_wrapper_pyclass(node, cclass, qualified_name, cclass_name, pyclass_name)
# allow the cclass methods to bind on instance of the pyclass
binding_decorator = Nodes.DecoratorNode(
node.pos,
decorator=ExprNodes.SimpleCallNode(
node.pos,
function=ExprNodes.AttributeNode(
node.pos,
attribute=EncodedString("binding"),
obj=ExprNodes.NameNode(node.pos, name=self.cython_as_name)
),
args=[ExprNodes.BoolNode(node.pos, value=True)]
)
)
cclass.decorators = [binding_decorator]
self.wrappers.append(cclass)
self.wrappers.append(pyclass)
# synthesize deferred dependent subclasses
for thunk in self.base_type_to_deferred[node_type]:
......@@ -263,24 +230,8 @@ def NAME(self, ARGDECLS):
bases_args = []
wrapped_bases_iterator = node_type.iter_wrapped_base_types()
try:
# consume the first wrapped base from the iterator
first_wrapped_base = next(wrapped_bases_iterator)
first_base_cclass_name = first_wrapped_base.wrapper_type.name
wrapped_first_base = ExprNodes.NameNode(node.pos, name=first_base_cclass_name)
bases_args.append(wrapped_first_base)
# use the pyclass wrapper for the other bases
for other_base in wrapped_bases_iterator:
_, __, other_base_pyclass_name = self.type_to_names[other_base]
other_base_arg = ExprNodes.NameNode(node.pos, name=other_base_pyclass_name)
bases_args.append(other_base_arg)
except StopIteration:
# no bases
pass
for base in node_type.iter_wrapped_base_types():
bases_args.append(ExprNodes.NameNode(node.pos, name=base.wrapper_type.name))
return ExprNodes.TupleNode(node.pos, args=bases_args)
......@@ -427,50 +378,6 @@ def NAME(self, ARGDECLS):
return method_wrapper
def synthesize_empty_slots(self, node):
lhs = ExprNodes.NameNode(node.pos, name=EncodedString("__slots__"))
rhs = ExprNodes.TupleNode(node.pos, args=[])
stat = Nodes.SingleAssignmentNode(node.pos, lhs=lhs, rhs=rhs)
return stat
def synthesize_wrapper_pyclass(self, node, cclass_wrapper, qualified_name, cclass_name, pyclass_name):
py_bases = self.synthesize_base_tuple(node)
# declare '__slots__ = ()' to allow this pyclass to be a base class
# of an extension type that doesn't define a 'cdef dict __dict__'.
py_empty_slots = self.synthesize_empty_slots(node)
py_stats = [py_empty_slots]
for defnode in cclass_wrapper.body.stats:
if isinstance(defnode, Nodes.DefNode):
def_name = defnode.name
lhs = ExprNodes.NameNode(defnode.pos, name=def_name)
rhs_obj = ExprNodes.NameNode(defnode.pos, name=cclass_name)
rhs = ExprNodes.AttributeNode(defnode.pos, obj=rhs_obj, attribute=def_name)
stat = Nodes.SingleAssignmentNode(defnode.pos, lhs=lhs, rhs=rhs)
py_stats.append(stat)
py_body = Nodes.StatListNode(cclass_wrapper.pos, stats=py_stats)
py_class_node = Nodes.PyClassDefNode(
cclass_wrapper.pos,
name=pyclass_name,
bases=py_bases,
keyword_args=None,
doc=cclass_wrapper.doc,
body=py_body,
decorators=None,
)
return py_class_node
#
# Utilities for cypclasses
......
......@@ -5305,8 +5305,17 @@ class CClassDefNode(ClassDefNode):
# At runtime, we check that the other bases are heap types
# and that a __dict__ is added if required.
for other_base in self.bases.args[1:]:
if other_base.analyse_as_type(env):
error(other_base.pos, "Only one extension type base class allowed.")
base_extension_type = other_base.analyse_as_type(env)
if base_extension_type:
if base_extension_type.attributes_known():
base_scope = base_extension_type.scope
# Allow other extension type bases if they only
# have python function or python property members.
npyfunc = len(base_scope.pyfunc_entries)
npyproperty = len([e for e in base_scope.property_entries if not e.is_cproperty])
if npyfunc + npyproperty == len(base_scope.entries):
continue
error(other_base.pos, "Only one extension type base class with non-empty C layout allowed.")
self.entry.type.early_init = 0
from . import ExprNodes
self.type_init_args = ExprNodes.TupleNode(
......
......@@ -38,11 +38,15 @@ static int __Pyx_PyType_Ready(PyTypeObject *t) {
b = (PyTypeObject*)b0;
if (!__Pyx_PyType_HasFeature(b, Py_TPFLAGS_HEAPTYPE))
{
__Pyx_TypeName b_name = __Pyx_PyType_GetName(b);
PyErr_Format(PyExc_TypeError,
"base class '" __Pyx_FMT_TYPENAME "' is not a heap type", b_name);
__Pyx_DECREF_TypeName(b_name);
return -1;
// Allow non-heap base types that strictly have the layout of a PyObject,
// as there is no chance of layout-conflict in that case.
if ( (b->tp_basicsize != sizeof(PyObject)) || (b->tp_itemsize != 0) ) {
__Pyx_TypeName b_name = __Pyx_PyType_GetName(b);
PyErr_Format(PyExc_TypeError,
"base class '" __Pyx_FMT_TYPENAME "' is not a heap type and has non-empty C layout", b_name);
__Pyx_DECREF_TypeName(b_name);
return -1;
}
}
if (t->tp_dictoffset == 0 && b->tp_dictoffset)
{
......
......@@ -13,9 +13,25 @@ setup(ext_modules=cythonize("*.pyx"))
cdef class Base:
pass
Obj = type(object())
NonEmptyLayout = type(list())
cdef class Foo(Base, Obj):
cdef class Foo(Base, NonEmptyLayout):
pass
######## notheaptype2.pyx ########
cdef class Base:
cdef int somelayout
cdef class Left(Base):
pass
cdef class Right(Base):
pass
Other = type(Right())
cdef class Diamond(Left, Other):
pass
######## wrongbase.pyx ########
......@@ -67,7 +83,13 @@ try:
import notheaptype
assert False
except TypeError as msg:
assert str(msg) == "base class 'object' is not a heap type"
assert str(msg) == "best base 'list' must be equal to first base 'notheaptype.Base'"
try:
import notheaptype2
assert False
except TypeError as msg:
assert str(msg) == "base class 'notheaptype2.Right' is not a heap type and has non-empty C layout"
try:
import wrongbase
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment