summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2016-12-30 15:42:50 +0200
committerClaudiu Popa <pcmanticore@gmail.com>2016-12-30 15:42:50 +0200
commite37acdc4bfe7e0d714e86a5c7f5d9f956cef15e5 (patch)
tree78fcf64c6af0d874ff9476d18e975efcd442c4d4
parent47779406d852d1951078e169a7210b533f7e2d03 (diff)
downloadastroid-git-e37acdc4bfe7e0d714e86a5c7f5d9f956cef15e5.tar.gz
Add a new mechanism for retrieving the special methods.
-rw-r--r--astroid/inference.py9
-rw-r--r--astroid/interpreter/dunder_lookup.py74
-rw-r--r--astroid/tests/unittest_inference.py6
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())