diff options
author | Claudiu Popa <pcmanticore@gmail.com> | 2016-03-03 21:19:55 +0000 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2016-06-04 10:07:36 +0100 |
commit | e1b66de78193b910707b665f1623d67dde86ac2f (patch) | |
tree | 44a233c2c1aa6a4324d6ade2d0c9de48eb41a8dc | |
parent | e3272214736449a585620c00ebfd80286f3a5500 (diff) | |
download | astroid-git-e1b66de78193b910707b665f1623d67dde86ac2f.tar.gz |
Exceptions have their own object model
Some of exceptions's attributes, such as .args and .message,
can't be inferred correctly since they are descriptors that get
transformed into the proper objects at runtime. This can cause issues
with the static analysis, since they are inferred as different than
what's expected. Now when we're creating instances of exceptions,
we're inferring a special object that knows how to transform those
runtime attributes into the proper objects via a custom object model.
Closes #81
-rw-r--r-- | ChangeLog | 11 | ||||
-rw-r--r-- | astroid/bases.py | 1 | ||||
-rw-r--r-- | astroid/interpreter/objectmodel.py | 27 | ||||
-rw-r--r-- | astroid/objects.py | 11 | ||||
-rw-r--r-- | astroid/protocols.py | 5 | ||||
-rw-r--r-- | astroid/tests/unittest_object_model.py | 42 |
6 files changed, 95 insertions, 2 deletions
@@ -30,6 +30,17 @@ Change log for the astroid package (used to be astng) objectmodel.py, which describes, in a more comprehensive way, the data model of each object. + * Exceptions have their own object model + + Some of exceptions's attributes, such as .args and .message, + can't be inferred correctly since they are descriptors that get + transformed into the proper objects at runtime. This can cause issues + with the static analysis, since they are inferred as different than + what's expected. Now when we're creating instances of exceptions, + we're inferring a special object that knows how to transform those + runtime attributes into the proper objects via a custom object model. + Closes issue #81 + * Fix a crash which occurred when a method had a same name as a builtin object, decorated at the same time by that builtin object ( a property for instance) diff --git a/astroid/bases.py b/astroid/bases.py index 126dc44b..43247f6c 100644 --- a/astroid/bases.py +++ b/astroid/bases.py @@ -271,7 +271,6 @@ class Instance(BaseInstance): pass - class UnboundMethod(Proxy): """a special node representing a method not bound to an instance""" diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py index cd79b961..2e8fedb7 100644 --- a/astroid/interpreter/objectmodel.py +++ b/astroid/interpreter/objectmodel.py @@ -41,6 +41,7 @@ mechanism. import itertools import pprint import os +import types import six @@ -537,3 +538,29 @@ class InstanceModel(ObjectModel): @property def py__dict__(self): return _dunder_dict(self._instance, self._instance.instance_attrs) + + +class ExceptionInstanceModel(InstanceModel): + + @property + def pyargs(self): + message = node_classes.Const('') + args = node_classes.Tuple(parent=self._instance) + args.postinit((message, )) + return args + + if six.PY3: + # It's available only on Python 3. + + @property + def py__traceback__(self): + builtins = astroid.MANAGER.builtins() + traceback_type = builtins[types.TracebackType.__name__] + return traceback_type.instantiate_class() + + if six.PY2: + # It's available only on Python 2. + + @property + def pymessage(self): + return node_classes.Const('') diff --git a/astroid/objects.py b/astroid/objects.py index 6597f3e8..83074664 100644 --- a/astroid/objects.py +++ b/astroid/objects.py @@ -169,3 +169,14 @@ class Super(node_classes.NodeNG): def getattr(self, name, context=None): return list(self.igetattr(name, context=context)) + + +class ExceptionInstance(bases.Instance): + """Class for instances of exceptions + + It has special treatment for some of the exceptions's attributes, + which are transformed at runtime into certain concrete objects, such as + the case of .args. + """ + + special_attributes = util.lazy_descriptor(lambda: objectmodel.ExceptionInstanceModel()) diff --git a/astroid/protocols.py b/astroid/protocols.py index 9efbad30..f7f90e4f 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -21,6 +21,8 @@ from astroid import helpers from astroid import nodes from astroid import util +raw_building = util.lazy_import('raw_building') +objects = util.lazy_import('objects') def _reflected_name(name): return "__r" + name[2:] @@ -384,7 +386,8 @@ def _resolve_asspart(parts, asspath, context): def excepthandler_assigned_stmts(self, node=None, context=None, asspath=None): for assigned in node_classes.unpack_infer(self.type): if isinstance(assigned, nodes.ClassDef): - assigned = bases.Instance(assigned) + assigned = objects.ExceptionInstance(assigned) + yield assigned # Explicit StopIteration to return error information, see comment # in raise_if_nothing_inferred. diff --git a/astroid/tests/unittest_object_model.py b/astroid/tests/unittest_object_model.py index 5eff4299..ec3f4e92 100644 --- a/astroid/tests/unittest_object_model.py +++ b/astroid/tests/unittest_object_model.py @@ -459,5 +459,47 @@ class GeneratorModelTest(unittest.TestCase): self.assertIsInstance(send, astroid.BoundMethod) +class ExceptionModelTest(unittest.TestCase): + + @unittest.skipIf(six.PY2, "needs Python 3") + def test_model_py3(self): + ast_nodes = test_utils.extract_node(''' + try: + x[42] + except ValueError as err: + err.args #@ + err.__traceback__ #@ + + err.message #@ + ''') + args = next(ast_nodes[0].infer()) + self.assertIsInstance(args, astroid.Tuple) + tb = next(ast_nodes[1].infer()) + self.assertIsInstance(tb, astroid.Instance) + self.assertEqual(tb.name, 'traceback') + + with self.assertRaises(exceptions.InferenceError): + next(ast_nodes[2].infer()) + + @unittest.skipUnless(six.PY2, "needs Python 2") + def test_model_py3(self): + ast_nodes = test_utils.extract_node(''' + try: + x[42] + except ValueError as err: + err.args #@ + err.message #@ + + err.__traceback__ #@ + ''') + args = next(ast_nodes[0].infer()) + self.assertIsInstance(args, astroid.Tuple) + message = next(ast_nodes[1].infer()) + self.assertIsInstance(message, astroid.Const) + + with self.assertRaises(exceptions.InferenceError): + next(ast_nodes[2].infer()) + + if __name__ == '__main__': unittest.main() |