diff options
author | cpopa <devnull@localhost> | 2014-04-29 22:41:20 +0300 |
---|---|---|
committer | cpopa <devnull@localhost> | 2014-04-29 22:41:20 +0300 |
commit | 5868ae7e21f4231cf1c07a0f8faf72b164544619 (patch) | |
tree | fe1c2634bb830a021b812dff8db30e68867461cd | |
parent | ee970b32970ce02e7a1ebd719a5513939db7256f (diff) | |
parent | ea057b7c54618eb1257279074a8b34b4511aa6d6 (diff) | |
download | astroid-5868ae7e21f4231cf1c07a0f8faf72b164544619.tar.gz |
Merge with default.
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | scoped_nodes.py | 33 | ||||
-rw-r--r-- | test/unittest_python3.py | 38 | ||||
-rw-r--r-- | test/unittest_scoped_nodes.py | 39 |
4 files changed, 110 insertions, 5 deletions
@@ -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): |