diff options
author | cpopa <devnull@localhost> | 2014-07-03 18:21:39 +0300 |
---|---|---|
committer | cpopa <devnull@localhost> | 2014-07-03 18:21:39 +0300 |
commit | 73d20c5d57eafb7b656de222437ef47ddb1e0936 (patch) | |
tree | f63744f9005b2f362fe0ae111b64b481e14d5548 | |
parent | 78a8ae0cd8b21cd58ce4aed75d3cd2889e5eb3ae (diff) | |
parent | 10dad945c622754052b436cc54245125d4d03ada (diff) | |
download | astroid-73d20c5d57eafb7b656de222437ef47ddb1e0936.tar.gz |
Merge with default.
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | bases.py | 2 | ||||
-rw-r--r-- | manager.py | 3 | ||||
-rw-r--r-- | rebuilder.py | 20 | ||||
-rw-r--r-- | scoped_nodes.py | 33 | ||||
-rw-r--r-- | test/unittest_python3.py | 16 | ||||
-rw-r--r-- | test/unittest_scoped_nodes.py | 48 |
7 files changed, 85 insertions, 40 deletions
@@ -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`). @@ -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, @@ -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 |