summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2014-04-29 22:41:20 +0300
committercpopa <devnull@localhost>2014-04-29 22:41:20 +0300
commit5868ae7e21f4231cf1c07a0f8faf72b164544619 (patch)
treefe1c2634bb830a021b812dff8db30e68867461cd
parentee970b32970ce02e7a1ebd719a5513939db7256f (diff)
parentea057b7c54618eb1257279074a8b34b4511aa6d6 (diff)
downloadastroid-5868ae7e21f4231cf1c07a0f8faf72b164544619.tar.gz
Merge with default.
-rw-r--r--ChangeLog5
-rw-r--r--scoped_nodes.py33
-rw-r--r--test/unittest_python3.py38
-rw-r--r--test/unittest_scoped_nodes.py39
4 files changed, 110 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index 7cc0ddc..68d0504 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,11 +1,14 @@
Change log for the astroid package (used to be astng)
=====================================================
+--
+ * `Class.metaclass()` looks in ancestors when the current class
+ does not define explicitly a metaclass.
+
* Do not cache modules if a module with the same qname is already
known, and only return cached modules if both name and filepath
match. Fixes pylint Bitbucket issue #136.
-
2014-04-18 -- 1.1.0
* All class nodes are marked as new style classes for Py3k.
diff --git a/scoped_nodes.py b/scoped_nodes.py
index 540e8a1..067f01f 100644
--- a/scoped_nodes.py
+++ b/scoped_nodes.py
@@ -1043,14 +1043,24 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
raise InferenceError()
_metaclass = None
- def metaclass(self):
- """ Return the metaclass of this class """
+ def _explicit_metaclass(self):
+ """ Return the explicit defined metaclass
+ for the current class.
+
+ 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.
+ """
if self._metaclass:
# Expects this from Py3k TreeRebuilder
try:
- return next(self._metaclass.infer())
+ infered = next(self._metaclass.infer())
except InferenceError:
- return
+ return
+ if infered is YES: # don't expose it
+ return None
+ return infered
try:
meta = self.getattr('__metaclass__')[0]
@@ -1063,3 +1073,18 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
if infered is YES: # don't expose this
return None
return infered
+
+ def metaclass(self):
+ """ Return the metaclass of this class.
+
+ If this class does not define explicitly a metaclass,
+ then the first defined metaclass in ancestors will be used
+ instead.
+ """
+ klass = self._explicit_metaclass()
+ if klass is None:
+ for parent in self.ancestors():
+ klass = parent.metaclass()
+ if klass is not None:
+ break
+ return klass
diff --git a/test/unittest_python3.py b/test/unittest_python3.py
index 96de632..9e80406 100644
--- a/test/unittest_python3.py
+++ b/test/unittest_python3.py
@@ -129,6 +129,44 @@ class Python3TC(TestCase):
metaclass = klass.metaclass()
self.assertIsInstance(metaclass, Class)
self.assertEqual(metaclass.name, 'type')
+
+ @require_version('3.0')
+ def test_metaclass_yes_leak(self):
+ astroid = self.builder.string_build(dedent("""
+ from ab import ABCMeta
+
+ class Meta(metaclass=ABCMeta): pass
+ """))
+ klass = astroid['Meta']
+ self.assertIsNone(klass.metaclass())
+
+ @require_version('3.0')
+ def test_metaclass_ancestors(self):
+ astroid = self.builder.string_build(dedent("""
+ from abc import ABCMeta
+
+ class FirstMeta(metaclass=ABCMeta): pass
+ class SecondMeta(metaclass=type):
+ pass
+
+ class Simple:
+ pass
+
+ class FirstImpl(FirstMeta): pass
+ class SecondImpl(FirstImpl): pass
+ class ThirdImpl(Simple, SecondMeta):
+ pass
+ """))
+ classes = {
+ 'ABCMeta': ('FirstImpl', 'SecondImpl'),
+ 'type': ('ThirdImpl', )
+ }
+ for metaclass, names in classes.items():
+ for name in names:
+ impl = astroid[name]
+ meta = impl.metaclass()
+ self.assertIsInstance(meta, Class)
+ self.assertEqual(meta.name, metaclass)
if __name__ == '__main__':
unittest_main()
diff --git a/test/unittest_scoped_nodes.py b/test/unittest_scoped_nodes.py
index f98d0ac..3d05318 100644
--- a/test/unittest_scoped_nodes.py
+++ b/test/unittest_scoped_nodes.py
@@ -716,6 +716,16 @@ def g2():
self.assertIsInstance(metaclass, scoped_nodes.Class)
self.assertEqual(metaclass.name, 'ABCMeta')
+ def test_metaclass_yes_leak(self):
+ astroid = abuilder.string_build(dedent("""
+ from ab import ABCMeta
+
+ class Meta(object):
+ __metaclass__ = ABCMeta
+ """))
+ klass = astroid['Meta']
+ self.assertIsNone(klass.metaclass())
+
@require_version('2.7')
def test_newstyle_and_metaclass_good(self):
astroid = abuilder.string_build(dedent("""
@@ -751,6 +761,35 @@ def g2():
self.assertIsInstance(metaclass, scoped_nodes.Class)
self.assertEqual(metaclass.name, 'ABCMeta')
+ def test_metaclass_ancestors(self):
+ astroid = abuilder.string_build(dedent("""
+ from abc import ABCMeta
+
+ class FirstMeta(object):
+ __metaclass__ = ABCMeta
+
+ class SecondMeta(object):
+ __metaclass__ = type
+
+ class Simple(object):
+ pass
+
+ class FirstImpl(FirstMeta): pass
+ class SecondImpl(FirstImpl): pass
+ class ThirdImpl(Simple, SecondMeta):
+ pass
+ """))
+ classes = {
+ 'ABCMeta': ('FirstImpl', 'SecondImpl'),
+ 'type': ('ThirdImpl', )
+ }
+ for metaclass, names in classes.items():
+ for name in names:
+ impl = astroid[name]
+ meta = impl.metaclass()
+ self.assertIsInstance(meta, nodes.Class)
+ self.assertEqual(meta.name, metaclass)
+
def test_nonregr_infer_callresult(self):
astroid = abuilder.string_build(dedent("""
class Delegate(object):