Skip to content

Commit

Permalink
Add support for inferring exception instances in all contexts
Browse files Browse the repository at this point in the history
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 pylint-dev/pylint#2333
  • Loading branch information
PCManticore committed Nov 17, 2019
1 parent 060fc2f commit b55fc8d
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 15 deletions.
11 changes: 11 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion astroid/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,))
Expand Down
4 changes: 2 additions & 2 deletions astroid/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
12 changes: 7 additions & 5 deletions astroid/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand Down
24 changes: 17 additions & 7 deletions tests/unittest_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down

0 comments on commit b55fc8d

Please sign in to comment.