diff options
-rw-r--r-- | ChangeLog | 6 | ||||
-rw-r--r-- | astroid/helpers.py | 2 | ||||
-rw-r--r-- | astroid/scoped_nodes.py | 13 | ||||
-rw-r--r-- | astroid/tests/unittest_inference.py | 14 |
4 files changed, 31 insertions, 4 deletions
@@ -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() |