summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog3
-rw-r--r--astroid/inference.py277
-rw-r--r--astroid/protocols.py129
-rw-r--r--astroid/tests/unittest_inference.py425
-rw-r--r--pylintrc2
5 files changed, 700 insertions, 136 deletions
diff --git a/ChangeLog b/ChangeLog
index c14cce6f..8879c084 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -191,6 +191,9 @@ Change log for the astroid package (used to be astng)
* Add helpers.is_supertype and helpers.is_subtype, two functions for
checking if an object is a super/sub type of another.
+ * Improve the inference of binary arithmetic operations (normal
+ and augmented).
+
2015-03-14 -- 1.3.6
diff --git a/astroid/inference.py b/astroid/inference.py
index 94ecd7eb..47eaa44a 100644
--- a/astroid/inference.py
+++ b/astroid/inference.py
@@ -18,10 +18,13 @@
"""this module contains a set of functions to handle inference on astroid trees
"""
+import functools
import itertools
import operator
+from astroid import helpers
from astroid import nodes
+from astroid import protocols
from astroid.manager import AstroidManager
from astroid.exceptions import (
AstroidError, InferenceError, NoDefault,
@@ -30,10 +33,7 @@ from astroid.exceptions import (
)
from astroid.bases import (YES, Instance, InferenceContext,
_infer_stmts, copy_context, path_wrapper,
- raise_if_nothing_infered)
-from astroid.protocols import (
- _arguments_infer_argname,
- BIN_OP_METHOD, UNARY_OP_METHOD)
+ raise_if_nothing_infered, yes_if_nothing_infered)
MANAGER = AstroidManager()
@@ -309,7 +309,7 @@ def _infer_unaryop(self, context=None):
# The operand doesn't support this operation.
yield UnaryOperationError(operand, self.op, exc)
except AttributeError as exc:
- meth = UNARY_OP_METHOD[self.op]
+ meth = protocols.UNARY_OP_METHOD[self.op]
if meth is None:
# `not node`. Determine node's boolean
# value and negate its result, unless it is
@@ -412,42 +412,246 @@ def _infer_boolop(self, context=None):
nodes.BoolOp._infer = _infer_boolop
-def _infer_binop(operator, operand1, operand2, context, failures=None):
- if operand1 is YES:
- yield operand1
- return
- try:
- for valnode in operand1.infer_binary_op(operator, operand2, context):
- yield valnode
- except AttributeError:
+# BinOp and AugAssign inferences
+
+def _is_not_implemented(const):
+ """Check if the given const node is NotImplemented."""
+ return isinstance(const, nodes.Const) and const.value is NotImplemented
+
+
+def _invoke_binop_inference(instance, op, other, context, method_name):
+ """Invoke binary operation inference on the given instance."""
+ method = instance.getattr(method_name)[0]
+ return instance.infer_binary_op(op, other, context, method)
+
+def _aug_op(instance, op, other, context, reverse=False):
+ """Get an inference callable for an augmented binary operation."""
+ method_name = protocols.AUGMENTED_OP_METHOD[op]
+ return functools.partial(_invoke_binop_inference,
+ instance=instance,
+ op=op, other=other,
+ context=context,
+ method_name=method_name)
+
+def _bin_op(instance, op, other, context, reverse=False):
+ """Get an inference callable for a normal binary operation.
+
+ If *reverse* is True, then the reflected method will be used instead.
+ """
+ if reverse:
+ method_name = protocols.REFLECTED_BIN_OP_METHOD[op]
+ else:
+ method_name = protocols.BIN_OP_METHOD[op]
+ return functools.partial(_invoke_binop_inference,
+ instance=instance,
+ op=op, other=other,
+ context=context,
+ method_name=method_name)
+
+
+def _get_binop_contexts(context, left, right):
+ """Get contexts for binary operations.
+
+ This will return two inferrence contexts, the first one
+ for x.__op__(y), the other one for y.__rop__(x), where
+ only the arguments are inversed.
+ """
+ # The order is important, since the first one should be
+ # left.__op__(right).
+ for arg in (right, left):
+ new_context = context.clone()
+ new_context.callcontext = CallContext(
+ [arg], starargs=None, dstarargs=None)
+ new_context.boundnode = None
+ yield new_context
+
+def _same_type(type1, type2):
+ """Check if type1 is the same as type2."""
+ return type1.qname() == type2.qname()
+
+
+def _get_binop_flow(left, left_type, op, right, right_type,
+ context, reverse_context):
+ """Get the flow for binary operations.
+
+ The rules are a bit messy:
+
+ * if left and right have the same type, then only one
+ method will be called, left.__op__(right)
+ * if left and right are unrelated typewise, then first
+ left.__op__(right) is tried and if this does not exist
+ or returns NotImplemented, then right.__rop__(left) is tried.
+ * if left is a subtype of right, then only left.__op__(right)
+ is tried.
+ * if left is a supertype of right, then right.__rop__(left)
+ is first tried and then left.__op__(right)
+ """
+ if _same_type(left_type, right_type):
+ methods = [_bin_op(left, op, right, context)]
+ elif helpers.is_subtype(left_type, right_type):
+ methods = [_bin_op(left, op, right, context)]
+ elif helpers.is_supertype(left_type, right_type):
+ methods = [_bin_op(right, op, left, reverse_context, reverse=True),
+ _bin_op(left, op, right, context)]
+ else:
+ methods = [_bin_op(left, op, right, context),
+ _bin_op(right, op, left, reverse_context, reverse=True)]
+ return methods
+
+
+def _get_aug_flow(left, left_type, aug_op, right, right_type,
+ context, reverse_context):
+ """Get the flow for augmented binary operations.
+
+ The rules are a bit messy:
+
+ * if left and right have the same type, then left.__augop__(right)
+ is first tried and then left.__op__(right).
+ * if left and right are unrelated typewise, then
+ left.__augop__(right) is tried, then left.__op__(right)
+ is tried and then right.__rop__(left) is tried.
+ * if left is a subtype of right, then left.__augop__(right)
+ is tried and then left.__op__(right).
+ * if left is a supertype of right, then left.__augop__(right)
+ is tried, then right.__rop__(left) and then
+ left.__op__(right)
+ """
+ op = aug_op.strip("=")
+ if _same_type(left_type, right_type):
+ methods = [_aug_op(left, aug_op, right, context),
+ _bin_op(left, op, right, context)]
+ elif helpers.is_subtype(left_type, right_type):
+ methods = [_aug_op(left, aug_op, right, context),
+ _bin_op(left, op, right, context)]
+ elif helpers.is_supertype(left_type, right_type):
+ methods = [_aug_op(left, aug_op, right, context),
+ _bin_op(right, op, left, reverse_context, reverse=True),
+ _bin_op(left, op, right, context)]
+ else:
+ methods = [_aug_op(left, aug_op, right, context),
+ _bin_op(left, op, right, context),
+ _bin_op(right, op, left, reverse_context, reverse=True)]
+ return methods
+
+
+def _infer_binary_operation(left, right, op, context, flow_factory):
+ """Infer a binary operation between a left operand and a right operand
+
+ This is used by both normal binary operations and augmented binary
+ operations, the only difference is the flow factory used.
+ """
+
+ context, reverse_context = _get_binop_contexts(context, left, right)
+ left_type = helpers.object_type(left)
+ right_type = helpers.object_type(right)
+ methods = flow_factory(left, left_type, op, right, right_type,
+ context, reverse_context)
+ for method in methods:
try:
- # XXX just suppose if the type implement meth, returned type
- # will be the same
- operand1.getattr(BIN_OP_METHOD[operator])
- yield operand1
- except Exception: # pylint: disable=broad-except
- if failures is None:
+ results = list(method())
+ except AttributeError:
+ continue
+ except NotFoundError:
+ continue
+ except InferenceError:
+ yield YES
+ return
+ else:
+ if any(result is YES for result in results):
yield YES
- else:
- failures.append(operand1)
+ return
+
+ # TODO(cpopa): since the inferrence engine might return
+ # more values than are actually possible, we decide
+ # to return YES if we have union types.
+ if all(map(_is_not_implemented, results)):
+ continue
+ not_implemented = sum(1 for result in results
+ if _is_not_implemented(result))
+ if not_implemented and not_implemented != len(results):
+ # Can't decide yet what this is, not yet though.
+ yield YES
+ return
+
+ for result in results:
+ yield result
+ return
+ # TODO(cpopa): yield a BinaryOperationError here,
+ # since the operation is not supported
+ yield YES
+
+
+def _infer_binop(self, context):
+ """Binary operation inferrence logic."""
+ left = self.left
+ right = self.right
+ op = self.op
+
+ for lhs in left.infer(context=context):
+ if lhs is YES:
+ # Don't know how to process this.
+ yield YES
+ return
+
+ # TODO(cpopa): if we have A() * A(), trying to infer
+ # the rhs with the same context will result in an
+ # inferrence error, so we create another context for it.
+ # This is a bug which should be fixed in InferenceContext at some point.
+ rhs_context = context.clone()
+ rhs_context.path = set()
+ for rhs in right.infer(context=rhs_context):
+ if rhs is YES:
+ # Don't know how to process this.
+ yield YES
+ return
+
+ results = _infer_binary_operation(lhs, rhs, op,
+ context, _get_binop_flow)
+ for result in results:
+ yield result
+
def infer_binop(self, context=None):
- failures = []
- for lhs in self.left.infer(context):
- for val in _infer_binop(self.op, lhs, self.right, context, failures):
- yield val
- for lhs in failures:
- for rhs in self.right.infer(context):
- for val in _infer_binop(self.op, rhs, lhs, context):
- yield val
-nodes.BinOp._infer = path_wrapper(infer_binop)
+ return _infer_binop(self, context)
+
+nodes.BinOp._infer = yes_if_nothing_infered(path_wrapper(infer_binop))
+def infer_augassign(self, context=None):
+ """Inferrence logic for augmented binary operations."""
+ op = self.op
+
+ for lhs in self.target.infer_lhs(context=context):
+ if lhs is YES:
+ # Don't know how to process this.
+ yield YES
+ return
+
+ # TODO(cpopa): if we have A() * A(), trying to infer
+ # the rhs with the same context will result in an
+ # inferrence error, so we create another context for it.
+ # This is a bug which should be fixed in InferenceContext at some point.
+ rhs_context = context.clone()
+ rhs_context.path = set()
+ for rhs in self.value.infer(context=rhs_context):
+ if rhs is YES:
+ # Don't know how to process this.
+ yield YES
+ return
+
+ results = _infer_binary_operation(lhs, rhs, op,
+ context, _get_aug_flow)
+ for result in results:
+ yield result
+
+
+nodes.AugAssign._infer = path_wrapper(infer_augassign)
+
def infer_arguments(self, context=None):
name = context.lookupname
if name is None:
raise InferenceError()
- return _arguments_infer_argname(self, name, context)
+ return protocols._arguments_infer_argname(self, name, context)
nodes.Arguments._infer = infer_arguments
@@ -463,17 +667,6 @@ def infer_ass(self, context=None):
nodes.AssName._infer = path_wrapper(infer_ass)
nodes.AssAttr._infer = path_wrapper(infer_ass)
-def infer_augassign(self, context=None):
- failures = []
- for lhs in self.target.infer_lhs(context):
- for val in _infer_binop(self.op, lhs, self.value, context, failures):
- yield val
- for lhs in failures:
- for rhs in self.value.infer(context):
- for val in _infer_binop(self.op, rhs, lhs, context):
- yield val
-nodes.AugAssign._infer = path_wrapper(infer_augassign)
-
# no infer method on DelName and DelAttr (expected InferenceError)
diff --git a/astroid/protocols.py b/astroid/protocols.py
index d9d36ae7..2cc0626f 100644
--- a/astroid/protocols.py
+++ b/astroid/protocols.py
@@ -23,6 +23,8 @@ import collections
import operator
import sys
+import six
+
from astroid.exceptions import InferenceError, NoDefault, NotFoundError
from astroid.node_classes import unpack_infer
from astroid.bases import (
@@ -34,10 +36,18 @@ from astroid.bases import (
from astroid.nodes import const_factory
from astroid import nodes
+
+def _reflected_name(name):
+ return "__r" + name[2:]
+
+def _augmented_name(name):
+ return "__i" + name[2:]
+
+
_CONTEXTLIB_MGR = 'contextlib.contextmanager'
BIN_OP_METHOD = {'+': '__add__',
'-': '__sub__',
- '/': '__div__',
+ '/': '__div__' if six.PY2 else '__truediv__',
'//': '__floordiv__',
'*': '__mul__',
'**': '__power__',
@@ -50,6 +60,15 @@ BIN_OP_METHOD = {'+': '__add__',
'@': '__matmul__'
}
+REFLECTED_BIN_OP_METHOD = {
+ key: _reflected_name(value)
+ for (key, value) in BIN_OP_METHOD.items()
+}
+AUGMENTED_OP_METHOD = {
+ key + "=": _augmented_name(value)
+ for (key, value) in BIN_OP_METHOD.items()
+}
+
UNARY_OP_METHOD = {'+': '__pos__',
'-': '__neg__',
'~': '__invert__',
@@ -74,9 +93,7 @@ nodes.Set.infer_unary_op = lambda self, op: _infer_unary_op(set(self.elts), op)
nodes.Const.infer_unary_op = lambda self, op: _infer_unary_op(self.value, op)
nodes.Dict.infer_unary_op = lambda self, op: _infer_unary_op(dict(self.items), op)
-
-
-# binary operations ###########################################################
+# Binary operations
BIN_OP_IMPL = {'+': lambda a, b: a + b,
'-': lambda a, b: a - b,
@@ -98,81 +115,53 @@ if sys.version_info >= (3, 5):
for _KEY, _IMPL in list(BIN_OP_IMPL.items()):
BIN_OP_IMPL[_KEY + '='] = _IMPL
-def const_infer_binary_op(self, operator, other, context):
- for other in other.infer(context):
- if isinstance(other, nodes.Const):
+def const_infer_binary_op(self, operator, other, context, _):
+ not_implemented = nodes.Const(NotImplemented)
+ if isinstance(other, nodes.Const):
+ try:
+ impl = BIN_OP_IMPL[operator]
try:
- impl = BIN_OP_IMPL[operator]
+ yield const_factory(impl(self.value, other.value))
+ except Exception: # pylint: disable=broad-except
+ # ArithmeticError is not enough: float >> float is a TypeError
+ yield not_implemented
+ except TypeError:
+ yield not_implemented
+ else:
+ yield not_implemented
- try:
- yield const_factory(impl(self.value, other.value))
- except Exception: # pylint: disable=broad-except
- # ArithmeticError is not enough: float >> float is a TypeError
- # TODO : let pylint know about the problem
- pass
- except TypeError:
- # XXX log TypeError
- continue
- elif other is YES:
- yield other
- else:
- try:
- for val in other.infer_binary_op(operator, self, context):
- yield val
- except AttributeError:
- yield YES
nodes.Const.infer_binary_op = yes_if_nothing_infered(const_infer_binary_op)
-def tl_infer_binary_op(self, operator, other, context):
- for other in other.infer(context):
- if isinstance(other, self.__class__) and operator == '+':
- node = self.__class__()
- elts = [n for elt in self.elts for n in elt.infer(context)
- if not n is YES]
- elts += [n for elt in other.elts for n in elt.infer(context)
- if not n is YES]
- node.elts = elts
- yield node
- elif isinstance(other, nodes.Const) and operator == '*':
- if not isinstance(other.value, int):
- yield YES
- continue
- node = self.__class__()
- elts = [n for elt in self.elts for n in elt.infer(context)
- if not n is YES] * other.value
- node.elts = elts
- yield node
- elif isinstance(other, Instance) and not isinstance(other, nodes.Const):
- yield YES
- # XXX else log TypeError
+def tl_infer_binary_op(self, operator, other, context, method):
+ not_implemented = nodes.Const(NotImplemented)
+ if isinstance(other, self.__class__) and operator == '+':
+ node = self.__class__()
+ elts = [n for elt in self.elts for n in elt.infer(context)
+ if not n is YES]
+ elts += [n for elt in other.elts for n in elt.infer(context)
+ if not n is YES]
+ node.elts = elts
+ yield node
+ elif isinstance(other, nodes.Const) and operator == '*':
+ if not isinstance(other.value, int):
+ yield not_implemented
+ return
+
+ node = self.__class__()
+ elts = [n for elt in self.elts for n in elt.infer(context)
+ if not n is YES] * other.value
+ node.elts = elts
+ yield node
+ else:
+ yield not_implemented
+
nodes.Tuple.infer_binary_op = yes_if_nothing_infered(tl_infer_binary_op)
nodes.List.infer_binary_op = yes_if_nothing_infered(tl_infer_binary_op)
-def dict_infer_binary_op(self, operator, other, context):
- for other in other.infer(context):
- if isinstance(other, Instance) and isinstance(other._proxied, nodes.Class):
- yield YES
- # XXX else log TypeError
-nodes.Dict.infer_binary_op = yes_if_nothing_infered(dict_infer_binary_op)
-
-def instance_infer_binary_op(self, operator, other, context):
- try:
- methods = self.getattr(BIN_OP_METHOD[operator])
- except (NotFoundError, KeyError):
- # Unknown operator
- yield YES
- else:
- for method in methods:
- if not isinstance(method, nodes.Function):
- continue
- for result in method.infer_call_result(self, context):
- if result is not YES:
- yield result
- # We are interested only in the first infered method,
- # don't go looking in the rest of the methods of the ancestors.
- break
+def instance_infer_binary_op(self, operator, other, context, method):
+ return method.infer_call_result(self, context)
Instance.infer_binary_op = yes_if_nothing_infered(instance_infer_binary_op)
diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py
index f63aca6a..c506121d 100644
--- a/astroid/tests/unittest_inference.py
+++ b/astroid/tests/unittest_inference.py
@@ -883,26 +883,65 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
ast = builder.string_build('a = "*" * 40', __name__, __file__)
self._test_const_infered(ast['a'], "*" * 40)
- def test_binary_op_bitand(self):
+ def test_binary_op_int_bitand(self):
ast = builder.string_build('a = 23&20', __name__, __file__)
self._test_const_infered(ast['a'], 23&20)
- def test_binary_op_bitor(self):
+ def test_binary_op_int_bitor(self):
ast = builder.string_build('a = 23|8', __name__, __file__)
self._test_const_infered(ast['a'], 23|8)
- def test_binary_op_bitxor(self):
+ def test_binary_op_int_bitxor(self):
ast = builder.string_build('a = 23^9', __name__, __file__)
self._test_const_infered(ast['a'], 23^9)
- def test_binary_op_shiftright(self):
+ def test_binary_op_int_shiftright(self):
ast = builder.string_build('a = 23 >>1', __name__, __file__)
self._test_const_infered(ast['a'], 23>>1)
- def test_binary_op_shiftleft(self):
+ def test_binary_op_int_shiftleft(self):
ast = builder.string_build('a = 23 <<1', __name__, __file__)
self._test_const_infered(ast['a'], 23<<1)
+ def test_binary_op_other_type(self):
+ ast_nodes = test_utils.extract_node('''
+ class A:
+ def __add__(self, other):
+ return other + 42
+ A() + 1 #@
+ 1 + A() #@
+ ''')
+ first = next(ast_nodes[0].infer())
+ self.assertIsInstance(first, nodes.Const)
+ self.assertEqual(first.value, 43)
+
+ second = next(ast_nodes[1].infer())
+ self.assertEqual(second, YES)
+
+ def test_binary_op_other_type_using_reflected_operands(self):
+ ast_nodes = test_utils.extract_node('''
+ class A(object):
+ def __radd__(self, other):
+ return other + 42
+ A() + 1 #@
+ 1 + A() #@
+ ''')
+ first = next(ast_nodes[0].infer())
+ self.assertEqual(first, YES)
+
+ second = next(ast_nodes[1].infer())
+ self.assertIsInstance(second, nodes.Const)
+ self.assertEqual(second.value, 43)
+
+ def test_binary_op_reflected_and_not_implemented_is_type_error(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __radd__(self, other): return NotImplemented
+
+ 1 + A() #@
+ ''')
+ first = next(ast_node.infer())
+ self.assertEqual(first, YES)
def test_binary_op_list_mul(self):
for code in ('a = [[]] * 2', 'a = 2 * [[]]'):
@@ -924,7 +963,6 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
self.assertEqual(len(infered), 1)
self.assertEqual(infered[0], YES)
-
def test_binary_op_tuple_add(self):
ast = builder.string_build('a = (1,) + (2,)', __name__, __file__)
infered = list(ast['a'].infer())
@@ -1197,34 +1235,22 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
def test_list_inference(self):
"""#20464"""
code = '''
- import optparse
-
+ from unknown import Unknown
A = []
B = []
def test():
xyz = [
- "foobar=%s" % options.ca,
+ Unknown
] + A + B
-
- if options.bind is not None:
- xyz.append("bind=%s" % options.bind)
return xyz
- def main():
- global options
-
- parser = optparse.OptionParser()
- (options, args) = parser.parse_args()
-
Z = test()
'''
ast = test_utils.build_module(code, __name__)
- infered = list(ast['Z'].infer())
- self.assertEqual(len(infered), 1, infered)
- self.assertIsInstance(infered[0], Instance)
- self.assertIsInstance(infered[0]._proxied, nodes.Class)
- self.assertEqual(infered[0]._proxied.name, 'list')
+ infered = next(ast['Z'].infer())
+ self.assertIsInstance(infered, nodes.List)
+ self.assertEqual(len(infered.elts), 0)
def test__new__(self):
code = '''
@@ -2128,6 +2154,359 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
inferred = next(node.infer())
self.assertEqual(inferred.value, expected)
+ def test_binop_same_types(self):
+ ast_nodes = test_utils.extract_node('''
+ class A(object):
+ def __add__(self, other):
+ return 42
+ 1 + 1 #@
+ 1 - 1 #@
+ "a" + "b" #@
+ A() + A() #@
+ ''')
+ expected_values = [2, 0, "ab", 42]
+ for node, expected in zip(ast_nodes, expected_values):
+ inferred = next(node.infer())
+ self.assertIsInstance(inferred, nodes.Const)
+ self.assertEqual(inferred.value, expected)
+
+ def test_binop_different_types_reflected_only(self):
+ node = test_utils.extract_node('''
+ class A(object):
+ pass
+ class B(object):
+ def __radd__(self, other):
+ return other
+ A() + B() #
+ ''')
+ inferred = next(node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'A')
+
+ def test_binop_different_types_normal_not_implemented_and_reflected(self):
+ node = test_utils.extract_node('''
+ class A(object):
+ def __add__(self, other):
+ return NotImplemented
+ class B(object):
+ def __radd__(self, other):
+ return other
+ A() + B() #
+ ''')
+ inferred = next(node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'A')
+
+ def test_binop_different_types_no_method_implemented(self):
+ node = test_utils.extract_node('''
+ class A(object):
+ pass
+ class B(object): pass
+ A() + B() #
+ ''')
+ inferred = next(node.infer())
+ self.assertEqual(inferred, YES)
+
+ def test_binop_different_types_reflected_and_normal_not_implemented(self):
+ node = test_utils.extract_node('''
+ class A(object):
+ def __add__(self, other): return NotImplemented
+ class B(object):
+ def __radd__(self, other): return NotImplemented
+ A() + B() #
+ ''')
+ inferred = next(node.infer())
+ self.assertEqual(inferred, YES)
+
+ def test_binop_subtype(self):
+ node = test_utils.extract_node('''
+ class A(object): pass
+ class B(A):
+ def __add__(self, other): return other
+ B() + A() #@
+ ''')
+ inferred = next(node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'A')
+
+ def test_binop_subtype_implemented_in_parent(self):
+ node = test_utils.extract_node('''
+ class A(object):
+ def __add__(self, other): return other
+ class B(A): pass
+ B() + A() #@
+ ''')
+ inferred = next(node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'A')
+
+ def test_binop_subtype_not_implemented(self):
+ node = test_utils.extract_node('''
+ class A(object):
+ pass
+ class B(A):
+ def __add__(self, other): return NotImplemented
+ B() + A() #@
+ ''')
+ inferred = next(node.infer())
+ self.assertEqual(inferred, YES)
+
+ def test_binop_supertype(self):
+ node = test_utils.extract_node('''
+ class A(object):
+ pass
+ class B(A):
+ def __radd__(self, other):
+ return other
+ A() + B() #@
+ ''')
+ inferred = next(node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'A')
+
+ def test_binop_supertype_rop_not_implemented(self):
+ node = test_utils.extract_node('''
+ class A(object):
+ def __add__(self, other):
+ return other
+ class B(A):
+ def __radd__(self, other):
+ return NotImplemented
+ A() + B() #@
+ ''')
+ inferred = next(node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'B')
+
+ def test_binop_supertype_both_not_implemented(self):
+ node = test_utils.extract_node('''
+ class A(object):
+ def __add__(self): return NotImplemented
+ class B(A):
+ def __radd__(self, other):
+ return NotImplemented
+ A() + B() #@
+ ''')
+ inferred = next(node.infer())
+ self.assertEqual(inferred, YES)
+
+ def test_binop_inferrence_errors(self):
+ ast_nodes = test_utils.extract_node('''
+ from unknown import Unknown
+ class A(object):
+ def __add__(self, other): return NotImplemented
+ class B(object):
+ def __add__(self, other): return Unknown
+ A() + Unknown #@
+ Unknown + A() #@
+ B() + A() #@
+ A() + B() #@
+ ''')
+ for node in ast_nodes:
+ self.assertEqual(next(node.infer()), YES)
+
+ def test_binop_ambiguity(self):
+ ast_nodes = test_utils.extract_node('''
+ class A(object):
+ def __add__(self, other):
+ if isinstance(other, B):
+ return NotImplemented
+ if type(other) is type(self):
+ return 42
+ return NotImplemented
+ class B(A): pass
+ class C(object):
+ def __radd__(self, other):
+ if isinstance(other, B):
+ return 42
+ return NotImplemented
+ A() + B() #@
+ B() + A() #@
+ A() + C() #@
+ C() + A() #@
+ ''')
+ for node in ast_nodes:
+ self.assertEqual(next(node.infer()), YES)
+
+ def test_aug_op_same_type_not_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return NotImplemented
+ def __add__(self, other): return NotImplemented
+ A() + A() #@
+ ''')
+ self.assertEqual(next(ast_node.infer()), YES)
+
+ def test_aug_op_same_type_aug_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return other
+ f = A()
+ f += A() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'A')
+
+ def test_aug_op_same_type_aug_not_implemented_normal_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return NotImplemented
+ def __add__(self, other): return 42
+ f = A()
+ f += A() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, nodes.Const)
+ self.assertEqual(inferred.value, 42)
+
+ def test_aug_op_subtype_both_not_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return NotImplemented
+ def __add__(self, other): return NotImplemented
+ class B(A):
+ pass
+ b = B()
+ b+=A() #@
+ ''')
+ self.assertEqual(next(ast_node.infer()), YES)
+
+ def test_aug_op_subtype_aug_op_is_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return 42
+ class B(A):
+ pass
+ b = B()
+ b+=A() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, nodes.Const)
+ self.assertEqual(inferred.value, 42)
+
+ def test_aug_op_subtype_normal_op_is_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __add__(self, other): return 42
+ class B(A):
+ pass
+ b = B()
+ b+=A() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, nodes.Const)
+ self.assertEqual(inferred.value, 42)
+
+ def test_aug_different_types_no_method_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object): pass
+ class B(object): pass
+ f = A()
+ f += B() #@
+ ''')
+ self.assertEqual(next(ast_node.infer()), YES)
+
+ def test_aug_different_types_augop_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return other
+ class B(object): pass
+ f = A()
+ f += B() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'B')
+
+ def test_aug_different_types_aug_not_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return NotImplemented
+ def __add__(self, other): return other
+ class B(object): pass
+ f = A()
+ f += B() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'B')
+
+ def test_aug_different_types_aug_not_implemented_rop_fallback(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return NotImplemented
+ def __add__(self, other): return NotImplemented
+ class B(object):
+ def __radd__(self, other): return other
+ f = A()
+ f += B() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'A')
+
+ def test_augop_supertypes_none_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object): pass
+ class B(object): pass
+ a = A()
+ a += B() #@
+ ''')
+ self.assertEqual(next(ast_node.infer()), YES)
+
+ def test_augop_supertypes_not_implemented_returned_for_all(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return NotImplemented
+ def __add__(self, other): return NotImplemented
+ class B(object):
+ def __add__(self, other): return NotImplemented
+ a = A()
+ a += B() #@
+ ''')
+ self.assertEqual(next(ast_node.infer()), YES)
+
+ def test_augop_supertypes_augop_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return other
+ class B(A): pass
+ a = A()
+ a += B() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'B')
+
+ def test_augop_supertypes_reflected_binop_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return NotImplemented
+ class B(A):
+ def __radd__(self, other): return other
+ a = A()
+ a += B() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'A')
+
+ def test_augop_supertypes_normal_binop_implemented(self):
+ ast_node = test_utils.extract_node('''
+ class A(object):
+ def __iadd__(self, other): return NotImplemented
+ def __add__(self, other): return other
+ class B(A):
+ def __radd__(self, other): return NotImplemented
+
+ a = A()
+ a += B() #@
+ ''')
+ inferred = next(ast_node.infer())
+ self.assertIsInstance(inferred, Instance)
+ self.assertEqual(inferred.name, 'B')
+
class GetattrTest(unittest.TestCase):
diff --git a/pylintrc b/pylintrc
index f3ad435c..18e062db 100644
--- a/pylintrc
+++ b/pylintrc
@@ -98,7 +98,7 @@ disable=invalid-name,protected-access,no-self-use,unused-argument,
too-many-return-statements,redefined-outer-name,undefined-variable,
too-many-locals,method-hidden,duplicate-code,attribute-defined-outside-init,
fixme,missing-docstring,too-many-lines,too-many-statements,undefined-loop-variable,
- unpacking-non-sequence,import-error,no-name-in-module,bad-builtin
+ unpacking-non-sequence,import-error,no-name-in-module,bad-builtin,too-many-arguments
[BASIC]