reimplement PEP 3155 __qualname__ calculation in a dedicated transform to base it...
authorStefan Behnel <stefan_ml@behnel.de>
Fri, 15 Nov 2013 20:38:45 +0000 (21:38 +0100)
committerStefan Behnel <stefan_ml@behnel.de>
Fri, 15 Nov 2013 20:38:45 +0000 (21:38 +0100)
CHANGES.rst
Cython/Compiler/ExprNodes.py
Cython/Compiler/ParseTreeTransforms.py
Cython/Compiler/Pipeline.py
Cython/Compiler/Symtab.py
tests/run/cyfunction.pyx

index 9585f39..84b69d9 100644 (file)
@@ -52,6 +52,9 @@ Features added
 Bugs fixed
 ----------
 
+* The PEP 3155 ``__qualname__`` was incorrect for nested classes and
+  inner classes/functions declared as ``global``.
+
 * 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.
index bb34ec1..d3e8098 100644 (file)
@@ -6699,21 +6699,9 @@ class SortedDictKeysNode(ExprNode):
 
 
 class ModuleNameMixin(object):
-    def set_qualified_name(self, env, self_name):
-        self.module_name = env.global_scope().qualified_name
-        qualified_name = [self_name]
-        entry = env.lookup(self_name)
-        if not entry or not (entry.is_pyglobal and not entry.is_pyclass_attr):
-            while env and not env.is_module_scope:
-                if env.is_closure_scope:
-                    qualified_name.append('<locals>')
-                qualified_name.append(env.name)
-                env = env.parent_scope
-        self.qualname = StringEncoding.EncodedString('.'.join(qualified_name[::-1]))
-
     def get_py_mod_name(self, code):
         return code.get_py_string_const(
-                 self.module_name, identifier=True)
+            self.module_name, identifier=True)
 
     def get_py_qualified_name(self, code):
         return code.get_py_string_const(
@@ -6741,8 +6729,6 @@ class ClassNode(ExprNode, ModuleNameMixin):
         self.type = py_object_type
         self.is_temp = 1
         env.use_utility_code(UtilityCode.load_cached("CreateClass", "ObjectHandling.c"))
-        #TODO(craig,haoyu) This should be moved to a better place
-        self.set_qualified_name(env, self.name)
         return self
 
     def may_be_none(self):
@@ -6984,8 +6970,6 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin):
             self.doc = self.doc.coerce_to_pyobject(env)
         self.type = py_object_type
         self.is_temp = 1
-        #TODO(craig,haoyu) This should be moved to a better place
-        self.set_qualified_name(env, self.name)
         return self
 
     def may_be_none(self):
@@ -7175,8 +7159,6 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
     def analyse_types(self, env):
         if self.binding:
             self.analyse_default_args(env)
-        #TODO(craig,haoyu) This should be moved to a better place
-        self.set_qualified_name(env, self.def_node.name)
         return self
 
     def analyse_default_args(self, env):
index bddd461..d009ea6 100644 (file)
@@ -1819,6 +1819,64 @@ if VALUE is not None:
         return property
 
 
+class CalculateQualifiedNamesTransform(EnvTransform):
+    """
+    Calculate and store the '__qualname__' and the global
+    module name on some nodes.
+    """
+    def visit_ModuleNode(self, node):
+        self.module_name = self.global_scope().qualified_name
+        self.qualified_name = []
+        self._super = super(CalculateQualifiedNamesTransform, self)
+        self.visitchildren(node)
+        return node
+
+    def _set_qualname(self, node, name=None):
+        if name:
+            qualname = self.qualified_name[:]
+            qualname.append(name)
+        else:
+            qualname = self.qualified_name
+        node.qualname = EncodedString('.'.join(qualname))
+        node.module_name = self.module_name
+        self.visitchildren(node)
+        return node
+
+    def _append_name(self, name):
+        if name == '<lambda>':
+            self.qualified_name.append('<lambda>')
+        else:
+            entry = self.current_env().lookup_here(name)
+            if entry.is_pyglobal and not entry.is_pyclass_attr:
+                self.qualified_name = [name]
+            else:
+                self.qualified_name.append(name)
+
+    def visit_ClassNode(self, node):
+        return self._set_qualname(node, node.name)
+
+    def visit_PyClassNamespaceNode(self, node):
+        return self._set_qualname(node)
+
+    def visit_PyCFunctionNode(self, node):
+        return self._set_qualname(node, node.def_node.name)
+
+    def visit_FuncDefNode(self, node):
+        orig_qualified_name = self.qualified_name[:]
+        self._append_name(node.name)
+        self.qualified_name.append('<locals>')
+        self._super.visit_FuncDefNode(node)
+        self.qualified_name = orig_qualified_name
+        return node
+
+    def visit_ClassDefNode(self, node):
+        orig_qualified_name = self.qualified_name[:]
+        self._append_name(node.name)
+        self._super.visit_ClassDefNode(node)
+        self.qualified_name = orig_qualified_name
+        return node
+
+
 class AnalyseExpressionsTransform(CythonTransform):
 
     def visit_ModuleNode(self, node):
index f4a0735..898b07c 100644 (file)
@@ -133,6 +133,7 @@ def create_pipeline(context, mode, exclude_classes=()):
     from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
     from ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods
     from ParseTreeTransforms import ExpandInplaceOperators, ParallelRangeTransform
+    from ParseTreeTransforms import CalculateQualifiedNamesTransform
     from TypeInference import MarkParallelAssignments, MarkOverflowingArithmetic
     from ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions
     from ParseTreeTransforms import RemoveUnreachableCode, GilCheck
@@ -193,6 +194,7 @@ def create_pipeline(context, mode, exclude_classes=()):
         _check_c_declarations,
         InlineDefNodeCalls(context),
         AnalyseExpressionsTransform(context),
+        CalculateQualifiedNamesTransform(context),
         FindInvalidUseOfFusedTypes(context),
         CreateClosureClasses(context),  ## After all lookups and type inference
         ExpandInplaceOperators(context),
index 16e2834..04bfdfc 100644 (file)
@@ -1468,6 +1468,7 @@ class ModuleScope(Scope):
         from TypeInference import PyObjectTypeInferer
         PyObjectTypeInferer().infer_types(self)
 
+
 class LocalScope(Scope):
 
     # Does the function have a 'with gil:' block?
@@ -1562,6 +1563,7 @@ class LocalScope(Scope):
                 entry.original_cname = entry.cname
                 entry.cname = "%s->%s" % (Naming.cur_scope_cname, entry.cname)
 
+
 class GeneratorExpressionScope(Scope):
     """Scope for generator expressions and comprehensions.  As opposed
     to generators, these can be easily inlined in some cases, so all
@@ -1660,6 +1662,7 @@ class StructOrUnionScope(Scope):
         return self.declare_var(name, type, pos,
                                 cname=cname, visibility=visibility)
 
+
 class ClassScope(Scope):
     #  Abstract base class for namespace of
     #  Python class or extension type.
@@ -2214,6 +2217,7 @@ class PropertyScope(Scope):
                 "in a property declaration")
             return None
 
+
 class CConstScope(Scope):
 
     def __init__(self, const_base_type_scope):
index 75f4260..ca6a32a 100644 (file)
@@ -47,35 +47,49 @@ def test_qualname():
 
 def test_nested_qualname():
     """
-    >>> func, lambda_func, XYZ = test_nested_qualname()
+    >>> outer, lambda_func, XYZ = test_nested_qualname()
 
-    >>> func().__qualname__
+    >>> outer().__qualname__
     'test_nested_qualname.<locals>.outer.<locals>.Test'
-    >>> func().test.__qualname__
+    >>> outer().test.__qualname__
     'test_nested_qualname.<locals>.outer.<locals>.Test.test'
-    >>> func()().test.__qualname__
+    >>> outer()().test.__qualname__
     'test_nested_qualname.<locals>.outer.<locals>.Test.test'
 
-    >>> func()().test().__qualname__
+    >>> outer()().test().__qualname__
     'XYZinner'
+    >>> outer()().test().Inner.__qualname__
+    'XYZinner.Inner'
+    >>> outer()().test().Inner.inner.__qualname__
+    'XYZinner.Inner.inner'
 
     >>> lambda_func.__qualname__
     'test_nested_qualname.<locals>.<lambda>'
 
     >>> XYZ.__qualname__
     'XYZ'
+    >>> XYZ.Inner.__qualname__
+    'XYZ.Inner'
+    >>> XYZ.Inner.inner.__qualname__
+    'XYZ.Inner.inner'
     """
     def outer():
         class Test(object):
             def test(self):
                 global XYZinner
-                class XYZinner(object): pass
+                class XYZinner:
+                    class Inner:
+                        def inner(self):
+                            pass
 
                 return XYZinner
         return Test
 
     global XYZ
-    class XYZ(object): pass
+    class XYZ(object):
+        class Inner(object):
+            def inner(self):
+                pass
 
     return outer, lambda:None, XYZ