summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--astroid/helpers.py2
-rw-r--r--astroid/scoped_nodes.py13
-rw-r--r--astroid/tests/unittest_inference.py14
4 files changed, 31 insertions, 4 deletions
diff --git a/ChangeLog b/ChangeLog
index 6a8e5091..389841b8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,12 @@ What's New in astroid 2.3.0?
============================
Release Date: TBA
+* Instances of exceptions are inferred as such when inferring in non-exception context
+
+ This allows special inference support for exception attributes such as `.args`.
+
+ Close PyCQA/pylint#2333
+
* Drop a superfluous and wrong callcontext when inferring the result of a context manager
Close PyCQA/pylint#2859
diff --git a/astroid/helpers.py b/astroid/helpers.py
index edbb205c..69d6efea 100644
--- a/astroid/helpers.py
+++ b/astroid/helpers.py
@@ -196,7 +196,7 @@ def _type_check(type1, type2):
def is_subtype(type1, type2):
- """Check if *type1* is a subtype of *typ2*."""
+ """Check if *type1* is a subtype of *type2*."""
return _type_check(type2, type1)
diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py
index 5dae6c41..bd0b7bd4 100644
--- a/astroid/scoped_nodes.py
+++ b/astroid/scoped_nodes.py
@@ -48,6 +48,8 @@ from astroid import util
BUILTINS = builtins.__name__
ITER_METHODS = ("__iter__", "__getitem__")
+EXCEPTION_BASE_CLASSES = frozenset({"Exception", "BaseException"})
+objects = util.lazy_import("objects")
def _c3_merge(sequences, cls, context):
@@ -2102,13 +2104,18 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
dunder_call = next(metaclass.igetattr("__call__", context))
except exceptions.AttributeInferenceError:
pass
+
if dunder_call and dunder_call.qname() != "builtins.type.__call__":
+ # Call type.__call__ if not set metaclass
+ # (since type is the default metaclass)
context = contextmod.bind_context_to_node(context, self)
yield from dunder_call.infer_call_result(caller, context)
else:
- # Call type.__call__ if not set metaclass
- # (since type is the default metaclass)
- yield bases.Instance(self)
+ 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)
def scope_lookup(self, node, name, offset=0):
"""Lookup where the given name is assigned.
diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py
index b388655c..eff9b148 100644
--- a/astroid/tests/unittest_inference.py
+++ b/astroid/tests/unittest_inference.py
@@ -5246,5 +5246,19 @@ def test_infer_context_manager_with_unknown_args():
assert isinstance(next(node.infer()), nodes.Const)
+def test_subclass_of_exception():
+ code = """
+ class Error(Exception):
+ pass
+
+ a = Error()
+ a
+ """
+ inferred = next(extract_node(code).infer())
+ assert isinstance(inferred, Instance)
+ args = next(inferred.igetattr("args"))
+ assert isinstance(args, nodes.Tuple)
+
+
if __name__ == "__main__":
unittest.main()