summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2014-07-03 18:21:39 +0300
committercpopa <devnull@localhost>2014-07-03 18:21:39 +0300
commit73d20c5d57eafb7b656de222437ef47ddb1e0936 (patch)
treef63744f9005b2f362fe0ae111b64b481e14d5548
parent78a8ae0cd8b21cd58ce4aed75d3cd2889e5eb3ae (diff)
parent10dad945c622754052b436cc54245125d4d03ada (diff)
downloadastroid-73d20c5d57eafb7b656de222437ef47ddb1e0936.tar.gz
Merge with default.
-rw-r--r--ChangeLog3
-rw-r--r--bases.py2
-rw-r--r--manager.py3
-rw-r--r--rebuilder.py20
-rw-r--r--scoped_nodes.py33
-rw-r--r--test/unittest_python3.py16
-rw-r--r--test/unittest_scoped_nodes.py48
7 files changed, 85 insertions, 40 deletions
diff --git a/ChangeLog b/ChangeLog
index f385cbd..f36a511 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,9 @@ Change log for the astroid package (used to be astng)
=====================================================
--
+ * `Class.metaclass()` now handles module-level __metaclass__ declaration
+ on python 2, and no longer looks at the __metaclass__ class attribute on
+ python 3.
* Function nodes can detect if they are decorated with subclasses
of builtin descriptors when determining their type
(`classmethod` and `staticmethod`).
diff --git a/bases.py b/bases.py
index 5ee11b3..fc4f572 100644
--- a/bases.py
+++ b/bases.py
@@ -387,7 +387,7 @@ class NodeNG(object):
return '%s(%s)' % (self.__class__.__name__, self._repr_name())
def __repr__(self):
- return '<%s(%s) l.%s [%s] at Ox%x>' % (self.__class__.__name__,
+ return '<%s(%s) l.%s [%s] at 0x%x>' % (self.__class__.__name__,
self._repr_name(),
self.fromlineno,
self.root().name,
diff --git a/manager.py b/manager.py
index 058e845..6e634b9 100644
--- a/manager.py
+++ b/manager.py
@@ -24,6 +24,7 @@ __docformat__ = "restructuredtext en"
import os
from os.path import dirname, join, isdir, exists
+from warnings import warn
from logilab.common.modutils import NoSourceFile, is_python_source, \
file_from_modpath, load_module_from_name, modpath_from_file, \
@@ -297,7 +298,7 @@ class AstroidManager(OptionsProviderMixIn):
if node is not orig_node:
# node has already be modified by some previous
# transformation, warn about it
- warn('node %s substitued multiple times' % node)
+ warn('node %s substituted multiple times' % node)
node = ret
return node
diff --git a/rebuilder.py b/rebuilder.py
index 40a614f..47eff50 100644
--- a/rebuilder.py
+++ b/rebuilder.py
@@ -116,12 +116,6 @@ def _set_infos(oldnode, newnode, parent):
newnode.col_offset = oldnode.col_offset
newnode.set_line_info(newnode.last_child()) # set_line_info accepts None
-def _infer_metaclass(node):
- if isinstance(node, Name):
- return node.id
- elif isinstance(node, Attribute):
- return node.attr
-
def _create_yield_node(node, parent, rebuilder, factory):
newnode = factory()
_lineno_parent(node, newnode, parent)
@@ -137,7 +131,6 @@ class TreeRebuilder(object):
def __init__(self, manager):
self._manager = manager
self.asscontext = None
- self._metaclass = ['']
self._global_names = []
self._from_nodes = []
self._delayed_assattr = []
@@ -246,9 +239,6 @@ class TreeRebuilder(object):
meth.extra_decorators.append(newnode.value)
except (AttributeError, KeyError):
continue
- elif getattr(newnode.targets[0], 'name', None) == '__metaclass__':
- # XXX check more...
- self._metaclass[-1] = _infer_metaclass(node.value)
newnode.set_line_info(newnode.last_child())
return newnode
@@ -321,7 +311,6 @@ class TreeRebuilder(object):
def visit_class(self, node, parent):
"""visit a Class node to become astroid"""
- self._metaclass.append(self._metaclass[-1])
newnode = new.Class(node.name, None)
_lineno_parent(node, newnode, parent)
_init_set_doc(node, newnode)
@@ -330,14 +319,6 @@ class TreeRebuilder(object):
if 'decorator_list' in node._fields and node.decorator_list:# py >= 2.6
newnode.decorators = self.visit_decorators(node, newnode)
newnode.set_line_info(newnode.last_child())
- metaclass = self._metaclass.pop()
- if PY3K:
- newnode._newstyle = True
- else:
- if not newnode.bases:
- # no base classes, detect new / style old style according to
- # current scope
- newnode._newstyle = metaclass in ('type', 'ABCMeta')
newnode.parent.frame().set_local(newnode.name, newnode)
return newnode
@@ -942,6 +923,7 @@ class TreeRebuilder3k(TreeRebuilder):
def visit_class(self, node, parent):
newnode = super(TreeRebuilder3k, self).visit_class(node, parent)
+ newnode._newstyle = True
for keyword in node.keywords:
if keyword.arg == 'metaclass':
newnode._metaclass = self.visit(keyword, newnode).value
diff --git a/scoped_nodes.py b/scoped_nodes.py
index ee917fa..88b8642 100644
--- a/scoped_nodes.py
+++ b/scoped_nodes.py
@@ -807,6 +807,11 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
if base._newstyle_impl(context):
self._newstyle = True
break
+ klass = self._explicit_metaclass()
+ # could be any callable, we'd need to infer the result of klass(name,
+ # bases, dict). punt if it's not a class node.
+ if klass is not None and isinstance(klass, Class):
+ self._newstyle = klass._newstyle_impl(context)
if self._newstyle is None:
self._newstyle = False
return self._newstyle
@@ -1081,8 +1086,9 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
An explicit defined metaclass is defined
either by passing the ``metaclass`` keyword argument
- in the class definition line (Python 3) or by
- having a ``__metaclass__`` class attribute.
+ in the class definition line (Python 3) or (Python 2) by
+ having a ``__metaclass__`` class attribute, or if there are
+ no explicit bases but there is a global ``__metaclass__`` variable.
"""
if self._metaclass:
# Expects this from Py3k TreeRebuilder
@@ -1090,14 +1096,25 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
return next(node for node in self._metaclass.infer()
if node is not YES)
except (InferenceError, StopIteration):
- return
+ return None
+ if sys.version_info >= (3, ):
+ return None
+
+ if '__metaclass__' in self.locals:
+ assignment = self.locals['__metaclass__'][-1]
+ elif self.bases:
+ return None
+ elif '__metaclass__' in self.root().locals:
+ assignments = [ass for ass in self.root().locals['__metaclass__']
+ if ass.lineno < self.lineno]
+ if not assignments:
+ return None
+ assignment = assignments[-1]
+ else:
+ return None
try:
- meta = self.getattr('__metaclass__')[0]
- except NotFoundError:
- return
- try:
- infered = meta.infer().next()
+ infered = assignment.infer().next()
except InferenceError:
return
if infered is YES: # don't expose this
diff --git a/test/unittest_python3.py b/test/unittest_python3.py
index 114e3d3..295984a 100644
--- a/test/unittest_python3.py
+++ b/test/unittest_python3.py
@@ -127,8 +127,7 @@ class Python3TC(TestCase):
"""))
klass = astroid['SubTest']
metaclass = klass.metaclass()
- self.assertIsInstance(metaclass, Class)
- self.assertEqual(metaclass.name, 'type')
+ self.assertIsNone(metaclass)
@require_version('3.0')
def test_metaclass_yes_leak(self):
@@ -142,6 +141,19 @@ class Python3TC(TestCase):
self.assertIsNone(klass.metaclass())
@require_version('3.0')
+ def test_parent_metaclass(self):
+ astroid = self.builder.string_build(dedent("""
+ from abc import ABCMeta
+ class Test(metaclass=ABCMeta): pass
+ class SubTest(Test): pass
+ """))
+ klass = astroid['SubTest']
+ self.assertTrue(klass.newstyle)
+ metaclass = klass.metaclass()
+ self.assertIsInstance(metaclass, Class)
+ self.assertEqual(metaclass.name, 'ABCMeta')
+
+ @require_version('3.0')
def test_metaclass_ancestors(self):
astroid = self.builder.string_build(dedent("""
from abc import ABCMeta
diff --git a/test/unittest_scoped_nodes.py b/test/unittest_scoped_nodes.py
index fd46a8b..dfcdd00 100644
--- a/test/unittest_scoped_nodes.py
+++ b/test/unittest_scoped_nodes.py
@@ -716,6 +716,8 @@ def g2():
self.assertEqual(astroid['g2'].tolineno, 10)
def test_simple_metaclass(self):
+ if PY3K:
+ self.skipTest('__metaclass__ syntax is python2-specific')
astroid = abuilder.string_build(dedent("""
class Test(object):
__metaclass__ = type
@@ -734,8 +736,9 @@ def g2():
klass = astroid['Test']
self.assertFalse(klass.metaclass())
- @require_version('2.7')
def test_metaclass_imported(self):
+ if PY3K:
+ self.skipTest('__metaclass__ syntax is python2-specific')
astroid = abuilder.string_build(dedent("""
from abc import ABCMeta
class Test(object):
@@ -758,8 +761,9 @@ def g2():
klass = astroid['Meta']
self.assertIsNone(klass.metaclass())
- @require_version('2.7')
def test_newstyle_and_metaclass_good(self):
+ if PY3K:
+ self.skipTest('__metaclass__ syntax is python2-specific')
astroid = abuilder.string_build(dedent("""
from abc import ABCMeta
class Test:
@@ -767,20 +771,44 @@ def g2():
"""))
klass = astroid['Test']
self.assertTrue(klass.newstyle)
-
- def test_newstyle_and_metaclass_bad(self):
+ self.assertEqual(klass.metaclass().name, 'ABCMeta')
astroid = abuilder.string_build(dedent("""
+ from abc import ABCMeta
+ __metaclass__ = ABCMeta
class Test:
- __metaclass__ = int
+ pass
"""))
klass = astroid['Test']
+ self.assertTrue(klass.newstyle)
+ self.assertEqual(klass.metaclass().name, 'ABCMeta')
+
+ def test_nested_metaclass(self):
if PY3K:
- self.assertTrue(klass.newstyle)
- else:
- self.assertFalse(klass.newstyle)
+ self.skipTest('__metaclass__ syntax is python2-specific')
+ astroid = abuilder.string_build(dedent("""
+ from abc import ABCMeta
+ class A(object):
+ __metaclass__ = ABCMeta
+ class B: pass
+
+ __metaclass__ = ABCMeta
+ class C:
+ __metaclass__ = type
+ class D: pass
+ """))
+ a = astroid['A']
+ b = a.locals['B'][0]
+ c = astroid['C']
+ d = c.locals['D'][0]
+ self.assertEqual(a.metaclass().name, 'ABCMeta')
+ self.assertFalse(b.newstyle)
+ self.assertIsNone(b.metaclass())
+ self.assertEqual(c.metaclass().name, 'type')
+ self.assertEqual(d.metaclass().name, 'ABCMeta')
- @require_version('2.7')
def test_parent_metaclass(self):
+ if PY3K:
+ self.skipTest('__metaclass__ syntax is python2-specific')
astroid = abuilder.string_build(dedent("""
from abc import ABCMeta
class Test:
@@ -794,6 +822,8 @@ def g2():
self.assertEqual(metaclass.name, 'ABCMeta')
def test_metaclass_ancestors(self):
+ if PY3K:
+ self.skipTest('__metaclass__ syntax is python2-specific')
astroid = abuilder.string_build(dedent("""
from abc import ABCMeta