Commit 766edcaa 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): ...@@ -120,7 +120,6 @@ def NAME(self, ARGDECLS):
self.synthesized = set() self.synthesized = set()
self.nesting_stack = [] self.nesting_stack = []
self.module_scope = node.scope self.module_scope = node.scope
self.cimport_cython = True
self.visitchildren(node) self.visitchildren(node)
self.inject_cypclass_wrappers(node) self.inject_cypclass_wrappers(node)
return node return node
...@@ -183,24 +182,10 @@ def NAME(self, ARGDECLS): ...@@ -183,24 +182,10 @@ def NAME(self, ARGDECLS):
nested_name = "_".join(nested_names) nested_name = "_".join(nested_names)
cclass_name = self.create_unique_name("%s_cyp_cclass_wrapper" % nested_name) 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): 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: for collected in self.collected_cypclasses:
self.synthesize_wrappers(collected) self.synthesize_wrappers(collected)
...@@ -224,7 +209,7 @@ def NAME(self, ARGDECLS): ...@@ -224,7 +209,7 @@ def NAME(self, ARGDECLS):
self.base_type_to_deferred[wrapped_base_type].append(lambda: self.synthesize_wrappers(node)) self.base_type_to_deferred[wrapped_base_type].append(lambda: self.synthesize_wrappers(node))
return 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) cclass = self.synthesize_wrapper_cclass(node, cclass_name, qualified_name)
...@@ -234,25 +219,7 @@ def NAME(self, ARGDECLS): ...@@ -234,25 +219,7 @@ def NAME(self, ARGDECLS):
# forward declare the cclass wrapper # forward declare the cclass wrapper
cclass.declare(self.module_scope) 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(cclass)
self.wrappers.append(pyclass)
# synthesize deferred dependent subclasses # synthesize deferred dependent subclasses
for thunk in self.base_type_to_deferred[node_type]: for thunk in self.base_type_to_deferred[node_type]:
...@@ -263,24 +230,8 @@ def NAME(self, ARGDECLS): ...@@ -263,24 +230,8 @@ def NAME(self, ARGDECLS):
bases_args = [] bases_args = []
wrapped_bases_iterator = node_type.iter_wrapped_base_types() for base in node_type.iter_wrapped_base_types():
bases_args.append(ExprNodes.NameNode(node.pos, name=base.wrapper_type.name))
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
return ExprNodes.TupleNode(node.pos, args=bases_args) return ExprNodes.TupleNode(node.pos, args=bases_args)
...@@ -427,50 +378,6 @@ def NAME(self, ARGDECLS): ...@@ -427,50 +378,6 @@ def NAME(self, ARGDECLS):
return method_wrapper 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 # Utilities for cypclasses
......
...@@ -5305,8 +5305,17 @@ class CClassDefNode(ClassDefNode): ...@@ -5305,8 +5305,17 @@ class CClassDefNode(ClassDefNode):
# At runtime, we check that the other bases are heap types # At runtime, we check that the other bases are heap types
# and that a __dict__ is added if required. # and that a __dict__ is added if required.
for other_base in self.bases.args[1:]: for other_base in self.bases.args[1:]:
if other_base.analyse_as_type(env): base_extension_type = other_base.analyse_as_type(env)
error(other_base.pos, "Only one extension type base class allowed.") 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 self.entry.type.early_init = 0
from . import ExprNodes from . import ExprNodes
self.type_init_args = ExprNodes.TupleNode( self.type_init_args = ExprNodes.TupleNode(
......
...@@ -38,11 +38,15 @@ static int __Pyx_PyType_Ready(PyTypeObject *t) { ...@@ -38,11 +38,15 @@ static int __Pyx_PyType_Ready(PyTypeObject *t) {
b = (PyTypeObject*)b0; b = (PyTypeObject*)b0;
if (!__Pyx_PyType_HasFeature(b, Py_TPFLAGS_HEAPTYPE)) if (!__Pyx_PyType_HasFeature(b, Py_TPFLAGS_HEAPTYPE))
{ {
__Pyx_TypeName b_name = __Pyx_PyType_GetName(b); // Allow non-heap base types that strictly have the layout of a PyObject,
PyErr_Format(PyExc_TypeError, // as there is no chance of layout-conflict in that case.
"base class '" __Pyx_FMT_TYPENAME "' is not a heap type", b_name); if ( (b->tp_basicsize != sizeof(PyObject)) || (b->tp_itemsize != 0) ) {
__Pyx_DECREF_TypeName(b_name); __Pyx_TypeName b_name = __Pyx_PyType_GetName(b);
return -1; PyErr_Format(PyExc_TypeError,
"base class '" __Pyx_FMT_TYPENAME "' is not a heap type", b_name);
__Pyx_DECREF_TypeName(b_name);
return -1;
}
} }
if (t->tp_dictoffset == 0 && b->tp_dictoffset) if (t->tp_dictoffset == 0 && b->tp_dictoffset)
{ {
......
...@@ -13,9 +13,25 @@ setup(ext_modules=cythonize("*.pyx")) ...@@ -13,9 +13,25 @@ setup(ext_modules=cythonize("*.pyx"))
cdef class Base: cdef class Base:
pass 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 pass
######## wrongbase.pyx ######## ######## wrongbase.pyx ########
...@@ -67,7 +83,13 @@ try: ...@@ -67,7 +83,13 @@ try:
import notheaptype import notheaptype
assert False assert False
except TypeError as msg: 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: try:
import wrongbase 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