implement metaclass calculation/validation algorithm, make classes inherit their...
authorStefan Behnel <stefan_ml@behnel.de>
Sun, 27 Oct 2013 14:53:43 +0000 (15:53 +0100)
committerStefan Behnel <stefan_ml@behnel.de>
Sun, 27 Oct 2013 14:53:43 +0000 (15:53 +0100)
CHANGES.rst
Cython/Compiler/ExprNodes.py
Cython/Compiler/Nodes.py
Cython/Compiler/Parsing.py
Cython/Utility/ObjectHandling.c
tests/run/locals_T732.pyx
tests/run/metaclass.pyx

index 23bc7ba..9585f39 100644 (file)
@@ -52,6 +52,16 @@ Features added
 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
   heterogeneous value types (e.g. mixing enum and char), allowing for
   a slightly wider application and reducing corner cases.  It now always
@@ -61,6 +71,9 @@ Bugs fixed
 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
   cleanup instead of ``tp_del()``.
 
index 3d91371..4551e1a 100644 (file)
@@ -6782,6 +6782,8 @@ class Py3ClassNode(ExprNode):
     #  name         EncodedString      Name of the class
     #  dict         ExprNode           Class dict (not owned by this node)
     #  module_name  EncodedString      Name of defining module
+    #  calculate_metaclass  bool       should call CalculateMetaclass()
+    #  allow_py2_metaclass  bool       should look for Py2 metaclass
 
     subexprs = []
 
@@ -6798,14 +6800,20 @@ class Py3ClassNode(ExprNode):
     def generate_result_code(self, code):
         code.globalstate.use_utility_code(UtilityCode.load_cached("Py3ClassCreate", "ObjectHandling.c"))
         cname = code.intern_identifier(self.name)
+        if self.mkw:
+            mkw = self.mkw.py_result()
+        else:
+            mkw = 'NULL'
         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.metaclass.result(),
                 cname,
                 self.bases.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.put_gotref(self.py_result())
 
@@ -6941,12 +6949,20 @@ class PyClassMetaclassNode(ExprNode):
         return True
 
     def generate_result_code(self, code):
-        code.globalstate.use_utility_code(UtilityCode.load_cached("Py3MetaclassGet", "ObjectHandling.c"))
-        code.putln(
-            "%s = __Pyx_Py3MetaclassGet(%s, %s); %s" % (
-                self.result(),
+        if self.mkw:
+            code.globalstate.use_utility_code(
+                UtilityCode.load_cached("Py3MetaclassGet", "ObjectHandling.c"))
+            call = "__Pyx_Py3MetaclassGet(%s, %s)" % (
                 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.put_gotref(self.py_result())
 
@@ -6983,6 +6999,10 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin):
             doc_code = self.doc.result()
         else:
             doc_code = '(PyObject *) NULL'
+        if self.mkw:
+            mkw = self.mkw.py_result()
+        else:
+            mkw = '(PyObject *) NULL'
         code.putln(
             "%s = __Pyx_Py3MetaclassPrepare(%s, %s, %s, %s, %s, %s, %s); %s" % (
                 self.result(),
@@ -6990,7 +7010,7 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin):
                 self.bases.result(),
                 cname,
                 qualname,
-                self.mkw.result(),
+                mkw,
                 py_mod_name,
                 doc_code,
                 code.error_goto_if_null(self.result(), self.pos)))
index 7633775..5310ad4 100644 (file)
@@ -3915,25 +3915,29 @@ class PyClassDefNode(ClassDefNode):
                    "target", "class_cell", "decorators"]
     decorators = 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,
-                 keyword_args = None, starstar_arg = None):
+    def __init__(self, pos, name, bases, doc, body, decorators=None,
+                 keyword_args=None, starstar_arg=None, force_py3_semantics=False):
         StatNode.__init__(self, pos)
         self.name = name
         self.doc = doc
         self.body = body
         self.decorators = decorators
+        self.bases = bases
         import ExprNodes
         if self.doc and Options.docstrings:
             doc = embed_position(self.pos, self.doc)
-            doc_node = ExprNodes.StringNode(pos, value = doc)
+            doc_node = ExprNodes.StringNode(pos, value=doc)
         else:
             doc_node = None
+
+        allow_py2_metaclass = not force_py3_semantics
         if keyword_args or starstar_arg:
-            self.py3_style_class = True
-            self.bases = bases
-            self.metaclass = None
+            allow_py2_metaclass = False
+            self.is_py3_style_class = True
             if keyword_args and not starstar_arg:
                 for i, item in list(enumerate(keyword_args.key_value_pairs))[::-1]:
                     if item.key.value == 'metaclass':
@@ -3946,36 +3950,50 @@ class PyClassDefNode(ClassDefNode):
                         del keyword_args.key_value_pairs[i]
             if starstar_arg:
                 self.mkw = ExprNodes.KeywordArgsNode(
-                    pos, keyword_args = keyword_args and keyword_args.key_value_pairs or [],
-                    starstar_arg = starstar_arg)
-            elif keyword_args and keyword_args.key_value_pairs:
+                    pos, keyword_args=keyword_args and keyword_args.key_value_pairs or [],
+                    starstar_arg=starstar_arg)
+            elif keyword_args.key_value_pairs:
                 self.mkw = keyword_args
             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 starstar_arg:
+                    # **kwargs may contain 'metaclass' arg
+                    mkdict = self.mkw
+                else:
+                    mkdict = None
                 self.metaclass = ExprNodes.PyClassMetaclassNode(
-                    pos, mkw = self.mkw, bases = self.bases)
-            self.dict = ExprNodes.PyClassNamespaceNode(pos, name = name,
-                        doc = doc_node, metaclass = self.metaclass, bases = self.bases,
-                        mkw = self.mkw)
-            self.classobj = ExprNodes.Py3ClassNode(pos, name = name,
-                    bases = self.bases, dict = self.dict, doc = doc_node,
-                    metaclass = self.metaclass, mkw = self.mkw)
+                    pos, mkw=mkdict, bases=self.bases)
+                needs_metaclass_calculation = False
+            else:
+                needs_metaclass_calculation = True
+
+            self.dict = ExprNodes.PyClassNamespaceNode(
+                pos, name=name, doc=doc_node,
+                metaclass=self.metaclass, bases=self.bases, mkw=self.mkw)
+            self.classobj = ExprNodes.Py3ClassNode(
+                pos, name=name,
+                bases=self.bases, dict=self.dict, doc=doc_node,
+                metaclass=self.metaclass, mkw=self.mkw,
+                calculate_metaclass=needs_metaclass_calculation,
+                allow_py2_metaclass=allow_py2_metaclass)
         else:
-            self.dict = ExprNodes.DictNode(pos, key_value_pairs = [])
-            self.metaclass = None
-            self.mkw = None
-            self.bases = None
-            self.classobj = ExprNodes.ClassNode(pos, name = name,
-                    bases = bases, dict = self.dict, doc = doc_node)
-        self.target = ExprNodes.NameNode(pos, name = name)
+            # 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)
 
     def as_cclass(self):
         """
         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")
             return
         bases = self.classobj.bases.args
@@ -4039,9 +4057,11 @@ class PyClassDefNode(ClassDefNode):
         self.body.analyse_declarations(cenv)
 
     def analyse_expressions(self, env):
-        if self.py3_style_class:
+        if self.bases:
             self.bases = self.bases.analyse_expressions(env)
+        if self.metaclass:
             self.metaclass = self.metaclass.analyse_expressions(env)
+        if self.mkw:
             self.mkw = self.mkw.analyse_expressions(env)
         self.dict = self.dict.analyse_expressions(env)
         self.class_result = self.class_result.analyse_expressions(env)
@@ -4059,9 +4079,11 @@ class PyClassDefNode(ClassDefNode):
     def generate_execution_code(self, code):
         code.pyclass_stack.append(self)
         cenv = self.scope
-        if self.py3_style_class:
+        if self.bases:
             self.bases.generate_evaluation_code(code)
+        if self.mkw:
             self.mkw.generate_evaluation_code(code)
+        if self.metaclass:
             self.metaclass.generate_evaluation_code(code)
         self.dict.generate_evaluation_code(code)
         cenv.namespace_cname = cenv.class_obj_cname = self.dict.result()
@@ -4075,11 +4097,13 @@ class PyClassDefNode(ClassDefNode):
         self.target.generate_assignment_code(self.class_result, code)
         self.dict.generate_disposal_code(code)
         self.dict.free_temps(code)
-        if self.py3_style_class:
-            self.mkw.generate_disposal_code(code)
-            self.mkw.free_temps(code)
+        if self.metaclass:
             self.metaclass.generate_disposal_code(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.free_temps(code)
         code.pyclass_stack.pop()
index a77a227..c84c778 100644 (file)
@@ -2962,12 +2962,13 @@ def p_class_statement(s, decorators):
         # XXX: empty arg_tuple
         arg_tuple = ExprNodes.TupleNode(pos, args=[])
     doc, body = p_suite_with_docstring(s, Ctx(level='class'))
-    return Nodes.PyClassDefNode(pos,
-        name = class_name,
-        bases = arg_tuple,
-        keyword_args = keyword_dict,
-        starstar_arg = starstar_arg,
-        doc = doc, body = body, decorators = decorators)
+    return Nodes.PyClassDefNode(
+        pos, name=class_name,
+        bases=arg_tuple,
+        keyword_args=keyword_dict,
+        starstar_arg=starstar_arg,
+        doc=doc, body=body, decorators=decorators,
+        force_py3_semantics=s.context.language_level >= 3)
 
 def p_c_class_definition(s, pos,  ctx):
     # s.sy == 'class'
index d7e3922..3dd9c59 100644 (file)
@@ -663,38 +663,93 @@ static CYTHON_INLINE PyObject* __Pyx_Py{{type}}_GetSlice(
 {{endfor}}
 #endif
 
-/////////////// FindPy2Metaclass.proto ///////////////
 
-static PyObject *__Pyx_FindPy2Metaclass(PyObject *bases); /*proto*/
+/////////////// CalculateMetaclass.proto ///////////////
 
-/////////////// FindPy2Metaclass ///////////////
+static PyObject *__Pyx_CalculateMetaclass(PyTypeObject *metaclass, PyObject *bases);
+
+/////////////// CalculateMetaclass ///////////////
+
+static PyObject *__Pyx_CalculateMetaclass(PyTypeObject *metaclass, PyObject *bases) {
+    Py_ssize_t i, nbases = PyTuple_GET_SIZE(bases);
+    for (i=0; i < nbases; i++) {
+        PyTypeObject *tmptype;
+        PyObject *tmp = PyTuple_GET_ITEM(bases, i);
+        tmptype = Py_TYPE(tmp);
+#if PY_MAJOR_VERSION < 3
+        if (tmptype == &PyClass_Type)
+            continue;
+#endif
+        if (!metaclass) {
+            metaclass = tmptype;
+            continue;
+        }
+        if (PyType_IsSubtype(metaclass, tmptype))
+            continue;
+        if (PyType_IsSubtype(tmptype, metaclass)) {
+            metaclass = tmptype;
+            continue;
+        }
+        // else:
+        PyErr_SetString(PyExc_TypeError,
+                        "metaclass conflict: "
+                        "the metaclass of a derived class "
+                        "must be a (non-strict) subclass "
+                        "of the metaclasses of all its bases");
+        return NULL;
+    }
+    if (!metaclass) {
+#if PY_MAJOR_VERSION < 3
+        metaclass = &PyClass_Type;
+#else
+        metaclass = &PyType_Type;
+#endif
+    }
+    // make owned reference
+    Py_INCREF((PyObject*) metaclass);
+    return (PyObject*) metaclass;
+}
+
+
+/////////////// FindInheritedMetaclass.proto ///////////////
+
+static PyObject *__Pyx_FindInheritedMetaclass(PyObject *bases); /*proto*/
+
+/////////////// FindInheritedMetaclass ///////////////
 //@requires: PyObjectGetAttrStr
+//@requires: CalculateMetaclass
 
-static PyObject *__Pyx_FindPy2Metaclass(PyObject *bases) {
+static PyObject *__Pyx_FindInheritedMetaclass(PyObject *bases) {
     PyObject *metaclass;
-    /* Default metaclass */
-#if PY_MAJOR_VERSION < 3
     if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
+        PyTypeObject *metatype;
         PyObject *base = PyTuple_GET_ITEM(bases, 0);
-        metaclass = __Pyx_PyObject_GetAttrStr(base, PYIDENT("__class__"));
-        if (!metaclass) {
+#if PY_MAJOR_VERSION < 3
+        PyObject* basetype = __Pyx_PyObject_GetAttrStr(base, PYIDENT("__class__"));
+        if (basetype) {
+            metatype = (PyType_Check(basetype)) ? ((PyTypeObject*) basetype) : NULL;
+        } else {
             PyErr_Clear();
-            metaclass = (PyObject*) Py_TYPE(base);
-            Py_INCREF(metaclass);
+            metatype = Py_TYPE(base);
+            basetype = (PyObject*) metatype;
+            Py_INCREF(basetype);
         }
+#else
+        metatype = Py_TYPE(base);
+#endif
+        metaclass = __Pyx_CalculateMetaclass(metatype, bases);
+#if PY_MAJOR_VERSION < 3
+        Py_DECREF(basetype);
+#endif
     } else {
+        // no bases => use default metaclass
+#if PY_MAJOR_VERSION < 3
         metaclass = (PyObject *) &PyClass_Type;
-        Py_INCREF(metaclass);
-    }
 #else
-    if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
-        PyObject *base = PyTuple_GET_ITEM(bases, 0);
-        metaclass = (PyObject*) Py_TYPE(base);
-    } else {
         metaclass = (PyObject *) &PyType_Type;
-    }
-    Py_INCREF(metaclass);
 #endif
+        Py_INCREF(metaclass);
+    }
     return metaclass;
 }
 
@@ -703,7 +758,8 @@ static PyObject *__Pyx_FindPy2Metaclass(PyObject *bases) {
 static PyObject *__Pyx_Py3MetaclassGet(PyObject *bases, PyObject *mkw); /*proto*/
 
 /////////////// Py3MetaclassGet ///////////////
-//@requires: FindPy2Metaclass
+//@requires: FindInheritedMetaclass
+//@requires: CalculateMetaclass
 
 static PyObject *__Pyx_Py3MetaclassGet(PyObject *bases, PyObject *mkw) {
     PyObject *metaclass = PyDict_GetItem(mkw, PYIDENT("metaclass"));
@@ -713,9 +769,14 @@ static PyObject *__Pyx_Py3MetaclassGet(PyObject *bases, PyObject *mkw) {
             Py_DECREF(metaclass);
             return NULL;
         }
+        if (PyType_Check(metaclass)) {
+            PyObject* orig = metaclass;
+            metaclass = __Pyx_CalculateMetaclass((PyTypeObject*) metaclass, bases);
+            Py_DECREF(orig);
+        }
         return metaclass;
     }
-    return __Pyx_FindPy2Metaclass(bases);
+    return __Pyx_FindInheritedMetaclass(bases);
 }
 
 /////////////// CreateClass.proto ///////////////
@@ -724,7 +785,8 @@ static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *na
                                    PyObject *qualname, PyObject *modname); /*proto*/
 
 /////////////// CreateClass ///////////////
-//@requires: FindPy2Metaclass
+//@requires: FindInheritedMetaclass
+//@requires: CalculateMetaclass
 
 static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name,
                                    PyObject *qualname, PyObject *modname) {
@@ -740,9 +802,16 @@ static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *na
     metaclass = PyDict_GetItem(dict, PYIDENT("__metaclass__"));
     if (metaclass) {
         Py_INCREF(metaclass);
+        if (PyType_Check(metaclass)) {
+            PyObject* orig = metaclass;
+            metaclass = __Pyx_CalculateMetaclass((PyTypeObject*) metaclass, bases);
+            Py_DECREF(orig);
+        }
     } else {
-        metaclass = __Pyx_FindPy2Metaclass(bases);
+        metaclass = __Pyx_FindInheritedMetaclass(bases);
     }
+    if (unlikely(!metaclass))
+        return NULL;
     result = PyObject_CallFunctionObjArgs(metaclass, name, bases, dict, NULL);
     Py_DECREF(metaclass);
     return result;
@@ -750,11 +819,14 @@ static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *na
 
 /////////////// Py3ClassCreate.proto ///////////////
 
-static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name, PyObject *qualname, PyObject *mkw, PyObject *modname, PyObject *doc); /*proto*/
-static PyObject *__Pyx_Py3ClassCreate(PyObject *metaclass, PyObject *name, PyObject *bases, PyObject *dict, PyObject *mkw); /*proto*/
+static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name, PyObject *qualname,
+                                           PyObject *mkw, PyObject *modname, PyObject *doc); /*proto*/
+static PyObject *__Pyx_Py3ClassCreate(PyObject *metaclass, PyObject *name, PyObject *bases, PyObject *dict,
+                                      PyObject *mkw, int calculate_metaclass, int allow_py2_metaclass); /*proto*/
 
 /////////////// Py3ClassCreate ///////////////
 //@requires: PyObjectGetAttrStr
+//@requires: CalculateMetaclass
 
 static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name,
                                            PyObject *qualname, PyObject *mkw, PyObject *modname, PyObject *doc) {
@@ -764,27 +836,28 @@ static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases,
 
     prep = __Pyx_PyObject_GetAttrStr(metaclass, PYIDENT("__prepare__"));
     if (!prep) {
-        if (!PyErr_ExceptionMatches(PyExc_AttributeError))
+        if (unlikely(!PyErr_ExceptionMatches(PyExc_AttributeError)))
             return NULL;
         PyErr_Clear();
-        return PyDict_New();
-    }
-    pargs = PyTuple_Pack(2, name, bases);
-    if (!pargs) {
+        ns = PyDict_New();
+    } else {
+        pargs = PyTuple_Pack(2, name, bases);
+        if (unlikely(!pargs)) {
+            Py_DECREF(prep);
+            return NULL;
+        }
+        ns = PyObject_Call(prep, pargs, mkw);
         Py_DECREF(prep);
-        return NULL;
+        Py_DECREF(pargs);
     }
-    ns = PyObject_Call(prep, pargs, mkw);
-    Py_DECREF(prep);
-    Py_DECREF(pargs);
 
-    if (ns == NULL)
+    if (unlikely(!ns))
         return NULL;
 
     /* Required here to emulate assignment order */
-    if (PyObject_SetItem(ns, PYIDENT("__module__"), modname) < 0) goto bad;
-    if (PyObject_SetItem(ns, PYIDENT("__qualname__"), qualname) < 0) goto bad;
-    if (doc && PyObject_SetItem(ns, PYIDENT("__doc__"), doc) < 0) goto bad;
+    if (unlikely(PyObject_SetItem(ns, PYIDENT("__module__"), modname) < 0)) goto bad;
+    if (unlikely(PyObject_SetItem(ns, PYIDENT("__qualname__"), qualname) < 0)) goto bad;
+    if (unlikely(doc && PyObject_SetItem(ns, PYIDENT("__doc__"), doc) < 0)) goto bad;
     return ns;
 bad:
     Py_DECREF(ns);
@@ -792,13 +865,48 @@ bad:
 }
 
 static PyObject *__Pyx_Py3ClassCreate(PyObject *metaclass, PyObject *name, PyObject *bases,
-                                      PyObject *dict, PyObject *mkw) {
-    PyObject *result;
-    PyObject *margs = PyTuple_Pack(3, name, bases, dict);
-    if (!margs)
-        return NULL;
-    result = PyObject_Call(metaclass, margs, mkw);
-    Py_DECREF(margs);
+                                      PyObject *dict, PyObject *mkw,
+                                      int calculate_metaclass, int allow_py2_metaclass) {
+    PyObject *result, *margs;
+    PyObject *py2_metaclass = NULL;
+    if (allow_py2_metaclass) {
+        /* honour Python2 __metaclass__ for backward compatibility */
+        py2_metaclass = PyObject_GetItem(dict, PYIDENT("__metaclass__"));
+        if (py2_metaclass) {
+            if (likely(PyType_Check(py2_metaclass))) {
+                metaclass = py2_metaclass;
+                calculate_metaclass = 1;
+            } else {
+                /* py2_metaclass != NULL => calculate_metaclass != 0 */
+                Py_DECREF(py2_metaclass);
+                py2_metaclass = NULL;
+            }
+        } else if (likely(PyErr_ExceptionMatches(PyExc_KeyError))) {
+            PyErr_Clear();
+        } else {
+            return NULL;
+        }
+    }
+    if (calculate_metaclass) {
+        if (py2_metaclass || PyType_Check(metaclass)) {
+            metaclass = __Pyx_CalculateMetaclass((PyTypeObject*) metaclass, bases);
+            Py_XDECREF(py2_metaclass);
+            if (unlikely(!metaclass))
+                return NULL;
+        } else {
+            Py_XDECREF(py2_metaclass);
+            calculate_metaclass = 0;
+        }
+    }
+    margs = PyTuple_Pack(3, name, bases, dict);
+    if (unlikely(!margs)) {
+        result = NULL;
+    } else {
+        result = PyObject_Call(metaclass, margs, mkw);
+        Py_DECREF(margs);
+    }
+    if (calculate_metaclass)
+        Py_DECREF(metaclass);
     return result;
 }
 
index 85e884f..aa5b1ad 100644 (file)
@@ -24,7 +24,7 @@ def test_class_locals_and_dir():
     >>> 'visible' in klass.locs and 'not_visible' not in klass.locs
     True
     >>> klass.names
-    ['visible']
+    ['__module__', '__qualname__', 'visible']
     """
     not_visible = 1234
     class Foo:
index 81c7a7a..d057486 100644 (file)
@@ -6,7 +6,7 @@ class Base(type):
         attrs['metaclass_was_here'] = True
         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):
     """
     >>> obj = Foo()
@@ -27,6 +27,7 @@ class ODict(dict):
 
 class Py3MetaclassPlusAttr(type):
     def __new__(cls, name, bases, attrs, **kwargs):
+        assert isinstance(attrs, ODict), str(type(attrs))
         for key, value in kwargs.items():
             attrs[key] = value
         attrs['metaclass_was_here'] = True
@@ -53,8 +54,21 @@ class Py3ClassMCOnly(object, metaclass=Py3MetaclassPlusAttr):
     """
     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):
     def __new__(cls, name, bases, attrs, **kwargs):
+        assert isinstance(attrs, ODict), str(type(attrs))
         for key, value in kwargs.items():
             attrs[key] = value
         return type.__new__(cls, name, bases, attrs)
@@ -80,6 +94,19 @@ class Py3Foo(object, metaclass=Py3Base, foo=123):
     """
     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}
 
 @cython.test_assert_path_exists("//PyClassMetaclassNode", "//Py3ClassNode")