diff options
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | astroid/inference.py | 277 | ||||
-rw-r--r-- | astroid/protocols.py | 129 | ||||
-rw-r--r-- | astroid/tests/unittest_inference.py | 425 | ||||
-rw-r--r-- | pylintrc | 2 |
5 files changed, 700 insertions, 136 deletions
@@ -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): @@ -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]
|