Commit f082a44b authored by Stefan Behnel's avatar Stefan Behnel

reimplement PEP 3155 __qualname__ calculation in a dedicated transform to base...

reimplement PEP 3155 __qualname__ calculation in a dedicated transform to base it on the node structure instead of the scopes (which fails for class scopes)
parent 91886a47
......@@ -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.
......
......@@ -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):
......
......@@ -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):
......
......@@ -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),
......
......@@ -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):
......
......@@ -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
......
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