From 130406463f3c978571a7aca6e33c985e17848f21 Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Mon, 25 Nov 2019 08:32:49 +0100 Subject: [PATCH] Scope the inference to the current bound node when inferring instances of classes When inferring instances of classes from arguments, such as ``self`` in a bound method, we could use as a hint the context's ``boundnode``, which indicates the instance from which the inference originated. As an example, a subclass that uses a parent's method which returns ``self``, will override the ``self`` to point to it instead of pointing to the parent class. Close PyCQA/pylint#3157 --- ChangeLog | 11 ++++++++ astroid/protocols.py | 2 ++ tests/unittest_inference.py | 52 +++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/ChangeLog b/ChangeLog index 86abec8baf..99457ef8e7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,17 @@ Release Date: TBA * Allow inferring positional only arguments. +* Scope the inference to the current bound node when inferring instances of classes + + When inferring instances of classes from arguments, such as ``self`` + in a bound method, we could use as a hint the context's ``boundnode``, + which indicates the instance from which the inference originated. + As an example, a subclass that uses a parent's method which returns + ``self``, will override the ``self`` to point to it instead of pointing + to the parent class. + + Close PyCQA/pylint#3157 + * Add support for inferring exception instances in all contexts We were able to infer exception instances as ``ExceptionInstance`` diff --git a/astroid/protocols.py b/astroid/protocols.py index 891af0f691..bd1fede1e8 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -324,6 +324,8 @@ def _arguments_infer_argname(self, name, context): is_metaclass = isinstance(cls, nodes.ClassDef) and cls.type == "metaclass" # If this is a metaclass, then the first argument will always # be the class, not an instance. + if context.boundnode and isinstance(context.boundnode, bases.Instance): + cls = context.boundnode._proxied if is_metaclass or functype == "classmethod": yield cls return diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index f8dc7a64d7..7d0e10b9b7 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -5370,5 +5370,57 @@ def __init__(self, index): assert isinstance(index[0], nodes.AssignAttr) +@pytest.mark.parametrize( + "code,instance_name", + [ + ( + """ + class A: + def __enter__(self): + return self + def __exit__(self, err_type, err, traceback): + return + class B(A): + pass + with B() as b: + b #@ + """, + "B", + ), + ( + """ + class A: + def __enter__(self): + return A() + def __exit__(self, err_type, err, traceback): + return + class B(A): + pass + with B() as b: + b #@ + """, + "A", + ), + ( + """ + class A: + def test(self): + return A() + class B(A): + def test(self): + return A.test(self) + B().test() + """, + "A", + ), + ], +) +def test_inference_is_limited_to_the_boundnode(code, instance_name): + node = extract_node(code) + inferred = next(node.infer()) + assert isinstance(inferred, Instance) + assert inferred.name == instance_name + + if __name__ == "__main__": unittest.main()