diff options
author | cpopa <devnull@localhost> | 2014-08-13 18:07:11 +0300 |
---|---|---|
committer | cpopa <devnull@localhost> | 2014-08-13 18:07:11 +0300 |
commit | 21f9b3823e1afae5d43cf109468948b35f3fc0bb (patch) | |
tree | 8a8a494a1c3d9c5bd4efbd2dbd5c4069f06f9e7a | |
parent | 6e2922b562c7beda407a2cb8e4f00d6d0e1db682 (diff) | |
download | astroid-21f9b3823e1afae5d43cf109468948b35f3fc0bb.tar.gz |
The inference engine handles binary operations (add, mul etc.) between instances.
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | inference.py | 26 | ||||
-rw-r--r-- | protocols.py | 41 | ||||
-rw-r--r-- | test/unittest_inference.py | 58 |
4 files changed, 104 insertions, 24 deletions
@@ -19,6 +19,9 @@ Change log for the astroid package (used to be astng) * Inference of the functional form of the enums takes into consideration the various inputs that enums accepts. + * The inference engine handles binary operations (add, mul etc.) + between instances. + 2014-07-25 -- 1.2.0 * Function nodes can detect decorator call chain and see if they are diff --git a/inference.py b/inference.py index c36f695..4186307 100644 --- a/inference.py +++ b/inference.py @@ -30,7 +30,9 @@ from astroid.exceptions import (AstroidError, InferenceError, NoDefault, from astroid.bases import (YES, Instance, InferenceContext, _infer_stmts, copy_context, path_wrapper, raise_if_nothing_infered) -from astroid.protocols import _arguments_infer_argname +from astroid.protocols import ( + _arguments_infer_argname, + BIN_OP_METHOD, UNARY_OP_METHOD) MANAGER = AstroidManager() @@ -292,13 +294,6 @@ def infer_subscript(self, context=None): nodes.Subscript._infer = path_wrapper(infer_subscript) nodes.Subscript.infer_lhs = raise_if_nothing_infered(infer_subscript) - -UNARY_OP_METHOD = {'+': '__pos__', - '-': '__neg__', - '~': '__invert__', - 'not': None, # XXX not '__nonzero__' - } - def infer_unaryop(self, context=None): for operand in self.operand.infer(context): try: @@ -321,21 +316,6 @@ def infer_unaryop(self, context=None): yield YES nodes.UnaryOp._infer = path_wrapper(infer_unaryop) - -BIN_OP_METHOD = {'+': '__add__', - '-': '__sub__', - '/': '__div__', - '//': '__floordiv__', - '*': '__mul__', - '**': '__power__', - '%': '__mod__', - '&': '__and__', - '|': '__or__', - '^': '__xor__', - '<<': '__lshift__', - '>>': '__rshift__', - } - def _infer_binop(operator, operand1, operand2, context, failures=None): if operand1 is YES: yield operand1 diff --git a/protocols.py b/protocols.py index d621ffb..e7703a0 100644 --- a/protocols.py +++ b/protocols.py @@ -21,13 +21,33 @@ where it makes sense. __doctype__ = "restructuredtext en" -from astroid.exceptions import InferenceError, NoDefault +from astroid.exceptions import InferenceError, NoDefault, NotFoundError from astroid.node_classes import unpack_infer from astroid.bases import copy_context, \ raise_if_nothing_infered, yes_if_nothing_infered, Instance, YES from astroid.nodes import const_factory from astroid import nodes +BIN_OP_METHOD = {'+': '__add__', + '-': '__sub__', + '/': '__div__', + '//': '__floordiv__', + '*': '__mul__', + '**': '__power__', + '%': '__mod__', + '&': '__and__', + '|': '__or__', + '^': '__xor__', + '<<': '__lshift__', + '>>': '__rshift__', + } + +UNARY_OP_METHOD = {'+': '__pos__', + '-': '__neg__', + '~': '__invert__', + 'not': None, # XXX not '__nonzero__' + } + # unary operations ############################################################ def tl_infer_unary_op(self, operator): @@ -133,6 +153,25 @@ def dict_infer_binary_op(self, operator, other, context): # 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 + +Instance.infer_binary_op = yes_if_nothing_infered(instance_infer_binary_op) + # assignment ################################################################## diff --git a/test/unittest_inference.py b/test/unittest_inference.py index 06f5abb..ea49e50 100644 --- a/test/unittest_inference.py +++ b/test/unittest_inference.py @@ -1257,5 +1257,63 @@ def test(*args, **kwargs): infered = func.infered()[0] self.assertIsInstance(infered, UnboundMethod) + def test_instance_binary_operations(self): + code = dedent(""" + class A(object): + def __mul__(self, other): + return 42 + a = A() + b = A() + sub = a - b + mul = a * b + """) + astroid = builder.string_build(code, __name__, __file__) + sub = astroid['sub'].infered()[0] + mul = astroid['mul'].infered()[0] + self.assertIs(sub, YES) + self.assertIsInstance(mul, nodes.Const) + self.assertEqual(mul.value, 42) + + def test_instance_binary_operations_parent(self): + code = dedent(""" + class A(object): + def __mul__(self, other): + return 42 + class B(A): + pass + a = B() + b = B() + sub = a - b + mul = a * b + """) + astroid = builder.string_build(code, __name__, __file__) + sub = astroid['sub'].infered()[0] + mul = astroid['mul'].infered()[0] + self.assertIs(sub, YES) + self.assertIsInstance(mul, nodes.Const) + self.assertEqual(mul.value, 42) + + def test_instance_binary_operations_multiple_methods(self): + code = dedent(""" + class A(object): + def __mul__(self, other): + return 42 + class B(A): + def __mul__(self, other): + return [42] + a = B() + b = B() + sub = a - b + mul = a * b + """) + astroid = builder.string_build(code, __name__, __file__) + sub = astroid['sub'].infered()[0] + mul = astroid['mul'].infered()[0] + self.assertIs(sub, YES) + self.assertIsInstance(mul, nodes.List) + self.assertIsInstance(mul.elts[0], nodes.Const) + self.assertEqual(mul.elts[0].value, 42) + + if __name__ == '__main__': unittest_main() |