From b55fc8d9731e3527ab9c4c34488a6471d0aca62f Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Sun, 17 Nov 2019 10:46:45 +0100 Subject: [PATCH] Add support for inferring exception instances in all contexts We were able to infer exception instances as ``ExceptionInstance`` only for a handful of cases, but not all. ``ExceptionInstance`` has support for better inference of `.args` and other exception related attributes that normal instances do not have. This additional support should remove certain false positives related to ``.args`` and other exception attributes in ``pylint``. Close PyCQA/pylint#2333 --- ChangeLog | 11 +++++++++++ astroid/arguments.py | 2 +- astroid/protocols.py | 4 ++-- astroid/scoped_nodes.py | 12 +++++++----- tests/unittest_inference.py | 24 +++++++++++++++++------- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index c0c3725cfd..86abec8baf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,17 @@ Release Date: TBA * Allow inferring positional only arguments. +* Add support for inferring exception instances in all contexts + + We were able to infer exception instances as ``ExceptionInstance`` + only for a handful of cases, but not all. ``ExceptionInstance`` has + support for better inference of `.args` and other exception related + attributes that normal instances do not have. + This additional support should remove certain false positives related + to ``.args`` and other exception attributes in ``pylint``. + + Close PyCQA/pylint#2333 + * Add more supported parameters to ``subprocess.check_output`` Close #722 diff --git a/astroid/arguments.py b/astroid/arguments.py index c4bdc6d926..0a85360549 100644 --- a/astroid/arguments.py +++ b/astroid/arguments.py @@ -209,7 +209,7 @@ def infer_argument(self, funcnode, name, context): if funcnode.type == "method": if not isinstance(boundnode, bases.Instance): - boundnode = bases.Instance(boundnode) + boundnode = boundnode.instantiate_class() return iter((boundnode,)) if funcnode.type == "classmethod": return iter((boundnode,)) diff --git a/astroid/protocols.py b/astroid/protocols.py index 84e3571b06..891af0f691 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -328,7 +328,7 @@ def _arguments_infer_argname(self, name, context): yield cls return if functype == "method": - yield bases.Instance(cls) + yield cls.instantiate_class() return if context and context.callcontext: @@ -341,7 +341,7 @@ def _arguments_infer_argname(self, name, context): vararg.parent = self if not self.arguments and self.parent.name == "__init__": cls = self.parent.parent.scope() - vararg.elts = [bases.Instance(cls)] + vararg.elts = [cls.instantiate_class()] yield vararg return if name == self.kwarg: diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index 043e479d0f..e2f794813f 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -2133,11 +2133,7 @@ def infer_call_result(self, caller, context=None): context = contextmod.bind_context_to_node(context, self) yield from dunder_call.infer_call_result(caller, context) else: - if any(cls.name in EXCEPTION_BASE_CLASSES for cls in self.mro()): - # Subclasses of exceptions can be exception instances - yield objects.ExceptionInstance(self) - else: - yield bases.Instance(self) + yield self.instantiate_class() def scope_lookup(self, node, name, offset=0): """Lookup where the given name is assigned. @@ -2347,6 +2343,12 @@ def instantiate_class(self): or self if this is not possible. :rtype: Instance or ClassDef """ + try: + if any(cls.name in EXCEPTION_BASE_CLASSES for cls in self.mro()): + # Subclasses of exceptions can be exception instances + return objects.ExceptionInstance(self) + except exceptions.MroError: + pass return bases.Instance(self) def getattr(self, name, context=None, class_context=True): diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index 9ce0c8d288..e8f9980799 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -5236,14 +5236,24 @@ def __exit__(self, exc_type, exc_value, traceback): assert isinstance(next(node.infer()), nodes.Const) -def test_subclass_of_exception(): - code = """ - class Error(Exception): - pass +@pytest.mark.parametrize( + "code", + [ + """ + class Error(Exception): + pass - a = Error() - a - """ + a = Error() + a #@ + """, + """ + class Error(Exception): + def method(self): + self #@ + """, + ], +) +def test_subclass_of_exception(code): inferred = next(extract_node(code).infer()) assert isinstance(inferred, Instance) args = next(inferred.igetattr("args"))