diff options
| author | Claudiu Popa <pcmanticore@gmail.com> | 2016-12-30 15:42:50 +0200 |
|---|---|---|
| committer | Claudiu Popa <pcmanticore@gmail.com> | 2016-12-30 15:42:50 +0200 |
| commit | e37acdc4bfe7e0d714e86a5c7f5d9f956cef15e5 (patch) | |
| tree | 78fcf64c6af0d874ff9476d18e975efcd442c4d4 | |
| parent | 47779406d852d1951078e169a7210b533f7e2d03 (diff) | |
| download | astroid-git-e37acdc4bfe7e0d714e86a5c7f5d9f956cef15e5.tar.gz | |
Add a new mechanism for retrieving the special methods.
| -rw-r--r-- | astroid/inference.py | 9 | ||||
| -rw-r--r-- | astroid/interpreter/dunder_lookup.py | 74 | ||||
| -rw-r--r-- | astroid/tests/unittest_inference.py | 6 |
3 files changed, 87 insertions, 2 deletions
diff --git a/astroid/inference.py b/astroid/inference.py index a5780682..4e46b4e1 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -20,6 +20,7 @@ from astroid import decorators from astroid import exceptions from astroid.interpreter import runtimeabc from astroid.interpreter import util as inferenceutil +from astroid.interpreter import dunder_lookup from astroid import protocols from astroid.tree import treeabc from astroid import util @@ -370,7 +371,13 @@ def infer_unaryop(self, context=None, nodes=None): continue try: - meth = operand.getattr(meth, context=context)[0] + try: + methods = dunder_lookup.lookup(operand, meth) + except exceptions.NotSupportedError: + yield util.BadUnaryOperationMessage(operand, self.op, exc) + continue + + meth = methods[0] inferred = next(meth.infer(context=context)) if inferred is util.Uninferable or not inferred.callable(): continue diff --git a/astroid/interpreter/dunder_lookup.py b/astroid/interpreter/dunder_lookup.py new file mode 100644 index 00000000..4e0bad9c --- /dev/null +++ b/astroid/interpreter/dunder_lookup.py @@ -0,0 +1,74 @@ +# Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com> +# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html +# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER + +"""Contains logic for retrieving special methods. + +This implementation does not rely on the dot attribute access +logic, found in ``.getattr()``. The difference between these two +is that the dunder methods are looked with the type slots +(you can find more about these here +http://lucumr.pocoo.org/2014/8/16/the-python-i-would-like-to-see/) +As such, the lookup for the special methods is actually simpler than +the dot attribute access. +""" +import itertools + +import astroid +from astroid import exceptions +from astroid import util +from astroid.interpreter import runtimeabc +from astroid.tree import treeabc + + +def _lookup_in_mro(node, name): + local_attrs = node.locals.get(name, []) + external_attrs = node.external_attrs.get(name, []) + attrs = itertools.chain(local_attrs, external_attrs) + + nodes = itertools.chain.from_iterable( + itertools.chain( + ancestor.locals.get(name, []), + ancestor.external_attrs.get(name, []) + ) + for ancestor in node.ancestors(recurs=True) + ) + values = list(itertools.chain(attrs, nodes)) + if not values: + raise exceptions.NotSupportedError + + return values + + +@util.singledispatch +def lookup(node, name): + """Lookup the given special method name in the given *node* + + If the special method was found, then a list of attributes + will be returned. Otherwise, `astroid.NotSupportedError` + is going to be raised. + """ + raise exceptions.NotSupportedError + + +@lookup.register(treeabc.ClassDef) +def _(node, name): + metaclass = node.metaclass() + if metaclass is None: + raise exceptions.NotSupportedError + + return _lookup_in_mro(metaclass, name) + + +@lookup.register(runtimeabc.Instance) +def _(node, name): + return _lookup_in_mro(node, name) + + +@lookup.register(runtimeabc.BuiltinInstance) +def _(node, name): + values = node.locals.get(name, []) + if not values: + raise exceptions.NotSupportedError + + return values diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py index dfe11ea6..4c13e841 100644 --- a/astroid/tests/unittest_inference.py +++ b/astroid/tests/unittest_inference.py @@ -2820,9 +2820,13 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): def test_unary_op_classes(self): ast_node = extract_node(''' - class A(object): + import six + class Meta(type): def __invert__(self): return 42 + @six.add_metaclass(Meta) + class A(object): + pass ~A ''') inferred = next(ast_node.infer()) |
