Commit c0c8f92b authored by Stefan Behnel's avatar Stefan Behnel

implement metaclass calculation/validation algorithm, make classes inherit their parents' metaclass

parent 25b78a7a
...@@ -52,6 +52,16 @@ Features added ...@@ -52,6 +52,16 @@ Features added
Bugs fixed Bugs fixed
---------- ----------
* The metaclass of a Python class was not inherited from its parent
class(es). It is now extracted from the list of base classes if not
provided explicitly using the Py3 ``metaclass`` keyword argument.
In Py2 compilation mode, a ``__metaclass__`` entry in the class
dict will still take precedence if not using Py3 metaclass syntax,
but only *after* creating the class dict (which may have been done
by a metaclass of a base class, see PEP 3115). It is generally
recommended to use the explicit Py3 syntax to define metaclasses
for Python types at compile time.
* The automatic C switch statement generation behaves more safely for * The automatic C switch statement generation behaves more safely for
heterogeneous value types (e.g. mixing enum and char), allowing for heterogeneous value types (e.g. mixing enum and char), allowing for
a slightly wider application and reducing corner cases. It now always a slightly wider application and reducing corner cases. It now always
...@@ -61,6 +71,9 @@ Bugs fixed ...@@ -61,6 +71,9 @@ Bugs fixed
Other changes Other changes
------------- -------------
* In Py3 compilation mode, Python2-style metaclasses declared by a
``__metaclass__`` class dict entry are ignored.
* In Py3.4+, the Cython generator type uses ``tp_finalize()`` for safer * In Py3.4+, the Cython generator type uses ``tp_finalize()`` for safer
cleanup instead of ``tp_del()``. cleanup instead of ``tp_del()``.
......
...@@ -6782,6 +6782,8 @@ class Py3ClassNode(ExprNode): ...@@ -6782,6 +6782,8 @@ class Py3ClassNode(ExprNode):
# name EncodedString Name of the class # name EncodedString Name of the class
# dict ExprNode Class dict (not owned by this node) # dict ExprNode Class dict (not owned by this node)
# module_name EncodedString Name of defining module # module_name EncodedString Name of defining module
# calculate_metaclass bool should call CalculateMetaclass()
# allow_py2_metaclass bool should look for Py2 metaclass
subexprs = [] subexprs = []
...@@ -6798,14 +6800,20 @@ class Py3ClassNode(ExprNode): ...@@ -6798,14 +6800,20 @@ class Py3ClassNode(ExprNode):
def generate_result_code(self, code): def generate_result_code(self, code):
code.globalstate.use_utility_code(UtilityCode.load_cached("Py3ClassCreate", "ObjectHandling.c")) code.globalstate.use_utility_code(UtilityCode.load_cached("Py3ClassCreate", "ObjectHandling.c"))
cname = code.intern_identifier(self.name) cname = code.intern_identifier(self.name)
if self.mkw:
mkw = self.mkw.py_result()
else:
mkw = 'NULL'
code.putln( code.putln(
'%s = __Pyx_Py3ClassCreate(%s, %s, %s, %s, %s); %s' % ( '%s = __Pyx_Py3ClassCreate(%s, %s, %s, %s, %s, %d, %d); %s' % (
self.result(), self.result(),
self.metaclass.result(), self.metaclass.result(),
cname, cname,
self.bases.py_result(), self.bases.py_result(),
self.dict.py_result(), self.dict.py_result(),
self.mkw.py_result(), mkw,
self.calculate_metaclass,
self.allow_py2_metaclass,
code.error_goto_if_null(self.result(), self.pos))) code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result()) code.put_gotref(self.py_result())
...@@ -6941,12 +6949,20 @@ class PyClassMetaclassNode(ExprNode): ...@@ -6941,12 +6949,20 @@ class PyClassMetaclassNode(ExprNode):
return True return True
def generate_result_code(self, code): def generate_result_code(self, code):
code.globalstate.use_utility_code(UtilityCode.load_cached("Py3MetaclassGet", "ObjectHandling.c")) if self.mkw:
code.putln( code.globalstate.use_utility_code(
"%s = __Pyx_Py3MetaclassGet(%s, %s); %s" % ( UtilityCode.load_cached("Py3MetaclassGet", "ObjectHandling.c"))
self.result(), call = "__Pyx_Py3MetaclassGet(%s, %s)" % (
self.bases.result(), self.bases.result(),
self.mkw.result(), self.mkw.result())
else:
code.globalstate.use_utility_code(
UtilityCode.load_cached("CalculateMetaclass", "ObjectHandling.c"))
call = "__Pyx_CalculateMetaclass(NULL, %s)" % (
self.bases.result())
code.putln(
"%s = %s; %s" % (
self.result(), call,
code.error_goto_if_null(self.result(), self.pos))) code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result()) code.put_gotref(self.py_result())
...@@ -6983,6 +6999,10 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin): ...@@ -6983,6 +6999,10 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin):
doc_code = self.doc.result() doc_code = self.doc.result()
else: else:
doc_code = '(PyObject *) NULL' doc_code = '(PyObject *) NULL'
if self.mkw:
mkw = self.mkw.py_result()
else:
mkw = '(PyObject *) NULL'
code.putln( code.putln(
"%s = __Pyx_Py3MetaclassPrepare(%s, %s, %s, %s, %s, %s, %s); %s" % ( "%s = __Pyx_Py3MetaclassPrepare(%s, %s, %s, %s, %s, %s, %s); %s" % (
self.result(), self.result(),
...@@ -6990,7 +7010,7 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin): ...@@ -6990,7 +7010,7 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin):
self.bases.result(), self.bases.result(),
cname, cname,
qualname, qualname,
self.mkw.result(), mkw,
py_mod_name, py_mod_name,
doc_code, doc_code,
code.error_goto_if_null(self.result(), self.pos))) code.error_goto_if_null(self.result(), self.pos)))
......
...@@ -3915,25 +3915,29 @@ class PyClassDefNode(ClassDefNode): ...@@ -3915,25 +3915,29 @@ class PyClassDefNode(ClassDefNode):
"target", "class_cell", "decorators"] "target", "class_cell", "decorators"]
decorators = None decorators = None
class_result = None class_result = None
py3_style_class = False # Python3 style class (bases+kwargs) is_py3_style_class = False # Python3 style class (kwargs)
metaclass = None
mkw = None
def __init__(self, pos, name, bases, doc, body, decorators = None, def __init__(self, pos, name, bases, doc, body, decorators=None,
keyword_args = None, starstar_arg = None): keyword_args=None, starstar_arg=None, force_py3_semantics=False):
StatNode.__init__(self, pos) StatNode.__init__(self, pos)
self.name = name self.name = name
self.doc = doc self.doc = doc
self.body = body self.body = body
self.decorators = decorators self.decorators = decorators
self.bases = bases
import ExprNodes import ExprNodes
if self.doc and Options.docstrings: if self.doc and Options.docstrings:
doc = embed_position(self.pos, self.doc) doc = embed_position(self.pos, self.doc)
doc_node = ExprNodes.StringNode(pos, value = doc) doc_node = ExprNodes.StringNode(pos, value=doc)
else: else:
doc_node = None doc_node = None
allow_py2_metaclass = not force_py3_semantics
if keyword_args or starstar_arg: if keyword_args or starstar_arg:
self.py3_style_class = True allow_py2_metaclass = False
self.bases = bases self.is_py3_style_class = True
self.metaclass = None
if keyword_args and not starstar_arg: if keyword_args and not starstar_arg:
for i, item in list(enumerate(keyword_args.key_value_pairs))[::-1]: for i, item in list(enumerate(keyword_args.key_value_pairs))[::-1]:
if item.key.value == 'metaclass': if item.key.value == 'metaclass':
...@@ -3946,36 +3950,50 @@ class PyClassDefNode(ClassDefNode): ...@@ -3946,36 +3950,50 @@ class PyClassDefNode(ClassDefNode):
del keyword_args.key_value_pairs[i] del keyword_args.key_value_pairs[i]
if starstar_arg: if starstar_arg:
self.mkw = ExprNodes.KeywordArgsNode( self.mkw = ExprNodes.KeywordArgsNode(
pos, keyword_args = keyword_args and keyword_args.key_value_pairs or [], pos, keyword_args=keyword_args and keyword_args.key_value_pairs or [],
starstar_arg = starstar_arg) starstar_arg=starstar_arg)
elif keyword_args and keyword_args.key_value_pairs: elif keyword_args.key_value_pairs:
self.mkw = keyword_args self.mkw = keyword_args
else: else:
self.mkw = ExprNodes.NullNode(pos) assert self.metaclass is not None
if force_py3_semantics or self.bases or self.mkw or self.metaclass:
if self.metaclass is None: if self.metaclass is None:
if starstar_arg:
# **kwargs may contain 'metaclass' arg
mkdict = self.mkw
else:
mkdict = None
self.metaclass = ExprNodes.PyClassMetaclassNode( self.metaclass = ExprNodes.PyClassMetaclassNode(
pos, mkw = self.mkw, bases = self.bases) pos, mkw=mkdict, bases=self.bases)
self.dict = ExprNodes.PyClassNamespaceNode(pos, name = name, needs_metaclass_calculation = False
doc = doc_node, metaclass = self.metaclass, bases = self.bases, else:
mkw = self.mkw) needs_metaclass_calculation = True
self.classobj = ExprNodes.Py3ClassNode(pos, name = name,
bases = self.bases, dict = self.dict, doc = doc_node, self.dict = ExprNodes.PyClassNamespaceNode(
metaclass = self.metaclass, mkw = self.mkw) pos, name=name, doc=doc_node,
else: metaclass=self.metaclass, bases=self.bases, mkw=self.mkw)
self.dict = ExprNodes.DictNode(pos, key_value_pairs = []) self.classobj = ExprNodes.Py3ClassNode(
self.metaclass = None pos, name=name,
self.mkw = None bases=self.bases, dict=self.dict, doc=doc_node,
self.bases = None metaclass=self.metaclass, mkw=self.mkw,
self.classobj = ExprNodes.ClassNode(pos, name = name, calculate_metaclass=needs_metaclass_calculation,
bases = bases, dict = self.dict, doc = doc_node) allow_py2_metaclass=allow_py2_metaclass)
self.target = ExprNodes.NameNode(pos, name = name) else:
# no bases, no metaclass => old style class creation
self.dict = ExprNodes.DictNode(pos, key_value_pairs=[])
self.classobj = ExprNodes.ClassNode(
pos, name=name,
bases=bases, dict=self.dict, doc=doc_node)
self.target = ExprNodes.NameNode(pos, name=name)
self.class_cell = ExprNodes.ClassCellInjectorNode(self.pos) self.class_cell = ExprNodes.ClassCellInjectorNode(self.pos)
def as_cclass(self): def as_cclass(self):
""" """
Return this node as if it were declared as an extension class Return this node as if it were declared as an extension class
""" """
if self.py3_style_class: if self.is_py3_style_class:
error(self.classobj.pos, "Python3 style class could not be represented as C class") error(self.classobj.pos, "Python3 style class could not be represented as C class")
return return
bases = self.classobj.bases.args bases = self.classobj.bases.args
...@@ -4039,9 +4057,11 @@ class PyClassDefNode(ClassDefNode): ...@@ -4039,9 +4057,11 @@ class PyClassDefNode(ClassDefNode):
self.body.analyse_declarations(cenv) self.body.analyse_declarations(cenv)
def analyse_expressions(self, env): def analyse_expressions(self, env):
if self.py3_style_class: if self.bases:
self.bases = self.bases.analyse_expressions(env) self.bases = self.bases.analyse_expressions(env)
if self.metaclass:
self.metaclass = self.metaclass.analyse_expressions(env) self.metaclass = self.metaclass.analyse_expressions(env)
if self.mkw:
self.mkw = self.mkw.analyse_expressions(env) self.mkw = self.mkw.analyse_expressions(env)
self.dict = self.dict.analyse_expressions(env) self.dict = self.dict.analyse_expressions(env)
self.class_result = self.class_result.analyse_expressions(env) self.class_result = self.class_result.analyse_expressions(env)
...@@ -4059,9 +4079,11 @@ class PyClassDefNode(ClassDefNode): ...@@ -4059,9 +4079,11 @@ class PyClassDefNode(ClassDefNode):
def generate_execution_code(self, code): def generate_execution_code(self, code):
code.pyclass_stack.append(self) code.pyclass_stack.append(self)
cenv = self.scope cenv = self.scope
if self.py3_style_class: if self.bases:
self.bases.generate_evaluation_code(code) self.bases.generate_evaluation_code(code)
if self.mkw:
self.mkw.generate_evaluation_code(code) self.mkw.generate_evaluation_code(code)
if self.metaclass:
self.metaclass.generate_evaluation_code(code) self.metaclass.generate_evaluation_code(code)
self.dict.generate_evaluation_code(code) self.dict.generate_evaluation_code(code)
cenv.namespace_cname = cenv.class_obj_cname = self.dict.result() cenv.namespace_cname = cenv.class_obj_cname = self.dict.result()
...@@ -4075,11 +4097,13 @@ class PyClassDefNode(ClassDefNode): ...@@ -4075,11 +4097,13 @@ class PyClassDefNode(ClassDefNode):
self.target.generate_assignment_code(self.class_result, code) self.target.generate_assignment_code(self.class_result, code)
self.dict.generate_disposal_code(code) self.dict.generate_disposal_code(code)
self.dict.free_temps(code) self.dict.free_temps(code)
if self.py3_style_class: if self.metaclass:
self.mkw.generate_disposal_code(code)
self.mkw.free_temps(code)
self.metaclass.generate_disposal_code(code) self.metaclass.generate_disposal_code(code)
self.metaclass.free_temps(code) self.metaclass.free_temps(code)
if self.mkw:
self.mkw.generate_disposal_code(code)
self.mkw.free_temps(code)
if self.bases:
self.bases.generate_disposal_code(code) self.bases.generate_disposal_code(code)
self.bases.free_temps(code) self.bases.free_temps(code)
code.pyclass_stack.pop() code.pyclass_stack.pop()
......
...@@ -2962,12 +2962,13 @@ def p_class_statement(s, decorators): ...@@ -2962,12 +2962,13 @@ def p_class_statement(s, decorators):
# XXX: empty arg_tuple # XXX: empty arg_tuple
arg_tuple = ExprNodes.TupleNode(pos, args=[]) arg_tuple = ExprNodes.TupleNode(pos, args=[])
doc, body = p_suite_with_docstring(s, Ctx(level='class')) doc, body = p_suite_with_docstring(s, Ctx(level='class'))
return Nodes.PyClassDefNode(pos, return Nodes.PyClassDefNode(
name = class_name, pos, name=class_name,
bases = arg_tuple, bases=arg_tuple,
keyword_args = keyword_dict, keyword_args=keyword_dict,
starstar_arg = starstar_arg, starstar_arg=starstar_arg,
doc = doc, body = body, decorators = decorators) doc=doc, body=body, decorators=decorators,
force_py3_semantics=s.context.language_level >= 3)
def p_c_class_definition(s, pos, ctx): def p_c_class_definition(s, pos, ctx):
# s.sy == 'class' # s.sy == 'class'
......
This diff is collapsed.
...@@ -24,7 +24,7 @@ def test_class_locals_and_dir(): ...@@ -24,7 +24,7 @@ def test_class_locals_and_dir():
>>> 'visible' in klass.locs and 'not_visible' not in klass.locs >>> 'visible' in klass.locs and 'not_visible' not in klass.locs
True True
>>> klass.names >>> klass.names
['visible'] ['__module__', '__qualname__', 'visible']
""" """
not_visible = 1234 not_visible = 1234
class Foo: class Foo:
......
...@@ -6,7 +6,7 @@ class Base(type): ...@@ -6,7 +6,7 @@ class Base(type):
attrs['metaclass_was_here'] = True attrs['metaclass_was_here'] = True
return type.__new__(cls, name, bases, attrs) return type.__new__(cls, name, bases, attrs)
@cython.test_fail_if_path_exists("//PyClassMetaclassNode", "//Py3ClassNode") @cython.test_assert_path_exists("//PyClassMetaclassNode", "//Py3ClassNode")
class Foo(object): class Foo(object):
""" """
>>> obj = Foo() >>> obj = Foo()
...@@ -27,6 +27,7 @@ class ODict(dict): ...@@ -27,6 +27,7 @@ class ODict(dict):
class Py3MetaclassPlusAttr(type): class Py3MetaclassPlusAttr(type):
def __new__(cls, name, bases, attrs, **kwargs): def __new__(cls, name, bases, attrs, **kwargs):
assert isinstance(attrs, ODict), str(type(attrs))
for key, value in kwargs.items(): for key, value in kwargs.items():
attrs[key] = value attrs[key] = value
attrs['metaclass_was_here'] = True attrs['metaclass_was_here'] = True
...@@ -53,8 +54,21 @@ class Py3ClassMCOnly(object, metaclass=Py3MetaclassPlusAttr): ...@@ -53,8 +54,21 @@ class Py3ClassMCOnly(object, metaclass=Py3MetaclassPlusAttr):
""" """
bar = 321 bar = 321
class Py3InheritedMetaclass(Py3ClassMCOnly):
"""
>>> obj = Py3InheritedMetaclass()
>>> obj.bar
345
>>> obj.metaclass_was_here
True
>>> obj._order
['__module__', '__qualname__', '__doc__', 'bar', 'metaclass_was_here']
"""
bar = 345
class Py3Base(type): class Py3Base(type):
def __new__(cls, name, bases, attrs, **kwargs): def __new__(cls, name, bases, attrs, **kwargs):
assert isinstance(attrs, ODict), str(type(attrs))
for key, value in kwargs.items(): for key, value in kwargs.items():
attrs[key] = value attrs[key] = value
return type.__new__(cls, name, bases, attrs) return type.__new__(cls, name, bases, attrs)
...@@ -80,6 +94,19 @@ class Py3Foo(object, metaclass=Py3Base, foo=123): ...@@ -80,6 +94,19 @@ class Py3Foo(object, metaclass=Py3Base, foo=123):
""" """
bar = 321 bar = 321
@cython.test_assert_path_exists("//PyClassMetaclassNode", "//Py3ClassNode")
class Py3FooInherited(Py3Foo, foo=567):
"""
>>> obj = Py3FooInherited()
>>> obj.foo
567
>>> obj.bar
321
>>> obj._order
['__module__', '__qualname__', '__doc__', 'bar', 'foo']
"""
bar = 321
kwargs = {'foo': 123, 'bar': 456} kwargs = {'foo': 123, 'bar': 456}
@cython.test_assert_path_exists("//PyClassMetaclassNode", "//Py3ClassNode") @cython.test_assert_path_exists("//PyClassMetaclassNode", "//Py3ClassNode")
......
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