Repository: cython/cython

Issue:
Cythonization of pure Python crashes with function that passes its argument splat to a class definition.
This doesn't translate properly:
```python3
def ClassMaker(*bases):
	class MadeClass(*bases):
		pass
	return MadeClass
```
```
Compiling testfile.py because it changed.
[1/1] Cythonizing testfile.py
Traceback (most recent call last):
  File "setup.py", line 12, in <module>
    distutils.core.setup(ext_modules=Cython.Build.cythonize(packpaths, nthreads=buildthreads, compiler_directives={'language_level': 3, 'profile': True}, annotate=True))
  File ".../Cython/Build/Dependencies.py", line 1096, in cythonize
    cythonize_one(*args)
  File ".../Cython/Build/Dependencies.py", line 1202, in cythonize_one
    result = compile_single(pyx_file, options, full_module_name=full_module_name)
  File ".../Cython/Compiler/Main.py", line 727, in compile_single
    return run_pipeline(source, options, full_module_name)
  File ".../Cython/Compiler/Main.py", line 515, in run_pipeline
    err, enddata = Pipeline.run_pipeline(pipeline, source)
  File ".../Cython/Compiler/Pipeline.py", line 355, in run_pipeline
    data = run(phase, data)
  File ".../Cython/Compiler/Pipeline.py", line 335, in run
    return phase(data)
  File ".../Cython/Compiler/Pipeline.py", line 52, in generate_pyx_code_stage
    module_node.process_implementation(options, result)
  File ".../Cython/Compiler/ModuleNode.py", line 143, in process_implementation
    self.generate_c_code(env, options, result)
  File ".../Cython/Compiler/ModuleNode.py", line 385, in generate_c_code
    self.body.generate_function_definitions(env, code)
  File ".../Cython/Compiler/Nodes.py", line 442, in generate_function_definitions
    stat.generate_function_definitions(env, code)
  File ".../Cython/Compiler/Nodes.py", line 442, in generate_function_definitions
    stat.generate_function_definitions(env, code)
  File ".../Cython/Compiler/Nodes.py", line 3176, in generate_function_definitions
    FuncDefNode.generate_function_definitions(self, env, code)
  File ".../Cython/Compiler/Nodes.py", line 1983, in generate_function_definitions
    self.generate_function_body(env, code)
  File ".../Cython/Compiler/Nodes.py", line 1745, in generate_function_body
    self.body.generate_execution_code(code)
  File ".../Cython/Compiler/Nodes.py", line 448, in generate_execution_code
    stat.generate_execution_code(code)
  File ".../Cython/Compiler/Nodes.py", line 4601, in generate_execution_code
    self.class_result.generate_evaluation_code(code)
  File ".../Cython/Compiler/ExprNodes.py", line 773, in generate_evaluation_code
    self.generate_result_code(code)
  File ".../Cython/Compiler/ExprNodes.py", line 9034, in generate_result_code
    self.bases.py_result(),
  File ".../Cython/Compiler/ExprNodes.py", line 525, in py_result
    return self.result_as(py_object_type)
  File ".../Cython/Compiler/ExprNodes.py", line 516, in result_as
    if (self.is_temp and self.type.is_pyobject and
AttributeError: 'NoneType' object has no attribute 'is_pyobject'
```

The two forms below compile fine though:

```python3
def ClassMaker(bases):
	class MadeClass(*bases):
		pass
	return MadeClass
```
```python3
def ClassMaker(*bases):
	class MadeClass(bases):
		pass
	return MadeClass
```


Discussion:

Resolving patch:
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -8938,12 +8938,11 @@ class ClassNode(ExprNode, ModuleNameMixin):
     #  a name, tuple of bases and class dictionary.
     #
     #  name         EncodedString      Name of the class
-    #  bases        ExprNode           Base class tuple
-    #  dict         ExprNode           Class dict (not owned by this node)
+    #  class_def_node  PyClassDefNode  PyClassDefNode defining this class
     #  doc          ExprNode or None   Doc string
     #  module_name  EncodedString      Name of defining module
 
-    subexprs = ['bases', 'doc']
+    subexprs = ['doc']
     type = py_object_type
     is_temp = True
 
@@ -8952,7 +8951,6 @@ def infer_type(self, env):
         return py_object_type
 
     def analyse_types(self, env):
-        self.bases = self.bases.analyse_types(env)
         if self.doc:
             self.doc = self.doc.analyse_types(env)
             self.doc = self.doc.coerce_to_pyobject(env)
@@ -8965,12 +8963,13 @@ def may_be_none(self):
     gil_message = "Constructing Python class"
 
     def generate_result_code(self, code):
+        class_def_node = self.class_def_node
         cname = code.intern_identifier(self.name)
 
         if self.doc:
             code.put_error_if_neg(self.pos,
                 'PyDict_SetItem(%s, %s, %s)' % (
-                    self.dict.py_result(),
+                    class_def_node.dict.py_result(),
                     code.intern_identifier(
                         StringEncoding.EncodedString("__doc__")),
                     self.doc.py_result()))
@@ -8979,8 +8978,8 @@ def generate_result_code(self, code):
         code.putln(
             '%s = __Pyx_CreateClass(%s, %s, %s, %s, %s); %s' % (
                 self.result(),
-                self.bases.py_result(),
-                self.dict.py_result(),
+                class_def_node.bases.py_result(),
+                class_def_node.dict.py_result(),
                 cname,
                 qualname,
                 py_mod_name,
@@ -8994,8 +8993,8 @@ class Py3ClassNode(ExprNode):
     #  a name, tuple of bases and class dictionary.
     #
     #  name         EncodedString      Name of the class
-    #  dict         ExprNode           Class dict (not owned by this node)
     #  module_name  EncodedString      Name of defining module
+    #  class_def_node  PyClassDefNode  PyClassDefNode defining this class
     #  calculate_metaclass  bool       should call CalculateMetaclass()
     #  allow_py2_metaclass  bool       should look for Py2 metaclass
 
@@ -9018,12 +9017,10 @@ def may_be_none(self):
     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'
-        if self.metaclass:
-            metaclass = self.metaclass.py_result()
+        class_def_node = self.class_def_node
+        mkw = class_def_node.mkw.py_result() if class_def_node.mkw else 'NULL'
+        if class_def_node.metaclass:
+            metaclass = class_def_node.metaclass.py_result()
         else:
             metaclass = "((PyObject*)&__Pyx_DefaultClassType)"
         code.putln(
@@ -9031,8 +9028,8 @@ def generate_result_code(self, code):
                 self.result(),
                 metaclass,
                 cname,
-                self.bases.py_result(),
-                self.dict.py_result(),
+                class_def_node.bases.py_result(),
+                class_def_node.dict.py_result(),
                 mkw,
                 self.calculate_metaclass,
                 self.allow_py2_metaclass,
@@ -9043,8 +9040,7 @@ def generate_result_code(self, code):
 class PyClassMetaclassNode(ExprNode):
     # Helper class holds Python3 metaclass object
     #
-    #  bases        ExprNode           Base class tuple (not owned by this node)
-    #  mkw          ExprNode           Class keyword arguments (not owned by this node)
+    #  class_def_node  PyClassDefNode  PyClassDefNode defining this class
 
     subexprs = []
 
@@ -9057,38 +9053,38 @@ def may_be_none(self):
         return True
 
     def generate_result_code(self, code):
-        if self.mkw:
+        bases = self.class_def_node.bases
+        mkw = self.class_def_node.mkw
+        if mkw:
             code.globalstate.use_utility_code(
                 UtilityCode.load_cached("Py3MetaclassGet", "ObjectHandling.c"))
             call = "__Pyx_Py3MetaclassGet(%s, %s)" % (
-                self.bases.result(),
-                self.mkw.result())
+                bases.result(),
+                mkw.result())
         else:
             code.globalstate.use_utility_code(
                 UtilityCode.load_cached("CalculateMetaclass", "ObjectHandling.c"))
             call = "__Pyx_CalculateMetaclass(NULL, %s)" % (
-                self.bases.result())
+                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())
 
+
 class PyClassNamespaceNode(ExprNode, ModuleNameMixin):
     # Helper class holds Python3 namespace object
     #
     # All this are not owned by this node
-    #  metaclass    ExprNode           Metaclass object
-    #  bases        ExprNode           Base class tuple
-    #  mkw          ExprNode           Class keyword arguments
+    #  class_def_node  PyClassDefNode  PyClassDefNode defining this class
     #  doc          ExprNode or None   Doc string (owned)
 
     subexprs = ['doc']
 
     def analyse_types(self, env):
         if self.doc:
-            self.doc = self.doc.analyse_types(env)
-            self.doc = self.doc.coerce_to_pyobject(env)
+            self.doc = self.doc.analyse_types(env).coerce_to_pyobject(env)
         self.type = py_object_type
         self.is_temp = 1
         return self
@@ -9100,23 +9096,16 @@ def generate_result_code(self, code):
         cname = code.intern_identifier(self.name)
         py_mod_name = self.get_py_mod_name(code)
         qualname = self.get_py_qualified_name(code)
-        if self.doc:
-            doc_code = self.doc.result()
-        else:
-            doc_code = '(PyObject *) NULL'
-        if self.mkw:
-            mkw = self.mkw.py_result()
-        else:
-            mkw = '(PyObject *) NULL'
-        if self.metaclass:
-            metaclass = self.metaclass.py_result()
-        else:
-            metaclass = "(PyObject *) NULL"
+        class_def_node = self.class_def_node
+        null = "(PyObject *) NULL"
+        doc_code = self.doc.result() if self.doc else null
+        mkw = class_def_node.mkw.py_result() if class_def_node.mkw else null
+        metaclass = class_def_node.metaclass.py_result() if class_def_node.metaclass else null
         code.putln(
             "%s = __Pyx_Py3MetaclassPrepare(%s, %s, %s, %s, %s, %s, %s); %s" % (
                 self.result(),
                 metaclass,
-                self.bases.result(),
+                class_def_node.bases.result(),
                 cname,
                 qualname,
                 mkw,
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -4503,26 +4503,22 @@ def __init__(self, pos, name, bases, doc, body, decorators=None,
                     pass  # no base classes => no inherited metaclass
                 else:
                     self.metaclass = ExprNodes.PyClassMetaclassNode(
-                        pos, mkw=mkdict, bases=self.bases)
+                        pos, class_def_node=self)
                 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)
+                pos, name=name, doc=doc_node, class_def_node=self)
             self.classobj = ExprNodes.Py3ClassNode(
-                pos, name=name,
-                bases=self.bases, dict=self.dict, doc=doc_node,
-                metaclass=self.metaclass, mkw=self.mkw,
+                pos, name=name, class_def_node=self, doc=doc_node,
                 calculate_metaclass=needs_metaclass_calculation,
                 allow_py2_metaclass=allow_py2_metaclass)
         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)
+                pos, name=name, class_def_node=self, doc=doc_node)
 
         self.target = ExprNodes.NameNode(pos, name=name)
         self.class_cell = ExprNodes.ClassCellInjectorNode(self.pos)
@@ -4540,7 +4536,7 @@ def as_cclass(self):
                              visibility='private',
                              module_name=None,
                              class_name=self.name,
-                             bases=self.classobj.bases or ExprNodes.TupleNode(self.pos, args=[]),
+                             bases=self.bases or ExprNodes.TupleNode(self.pos, args=[]),
                              decorators=self.decorators,
                              body=self.body,
                              in_pxd=False,
@@ -4564,6 +4560,10 @@ def analyse_declarations(self, env):
                     args=[class_result])
             self.decorators = None
         self.class_result = class_result
+        if self.bases:
+            self.bases.analyse_declarations(env)
+        if self.mkw:
+            self.mkw.analyse_declarations(env)
         self.class_result.analyse_declarations(env)
         self.target.analyse_target_declaration(env)
         cenv = self.create_scope(env)
@@ -4574,10 +4574,10 @@ def analyse_declarations(self, env):
     def analyse_expressions(self, env):
         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)
+        if self.metaclass:
+            self.metaclass = self.metaclass.analyse_expressions(env)
         self.dict = self.dict.analyse_expressions(env)
         self.class_result = self.class_result.analyse_expressions(env)
         cenv = self.scope


Test patch:
diff --git a/tests/run/pyclass_dynamic_bases.pyx b/tests/run/pyclass_dynamic_bases.pyx
--- a/tests/run/pyclass_dynamic_bases.pyx
+++ b/tests/run/pyclass_dynamic_bases.pyx
@@ -24,3 +24,22 @@ def cond_if_bases(x):
     class PyClass(A if x else B):
         p = 5
     return PyClass
+
+
+def make_subclass(*bases):
+    """
+    >>> cls = make_subclass(list)
+    >>> issubclass(cls, list) or cls.__mro__
+    True
+
+    >>> class Cls(object): pass
+    >>> cls = make_subclass(Cls, list)
+    >>> issubclass(cls, list) or cls.__mro__
+    True
+    >>> issubclass(cls, Cls) or cls.__mro__
+    True
+    """
+    # GH-3338
+    class MadeClass(*bases):
+        pass
+    return MadeClass