summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2016-03-03 21:19:55 +0000
committerClaudiu Popa <pcmanticore@gmail.com>2016-06-04 10:07:36 +0100
commite1b66de78193b910707b665f1623d67dde86ac2f (patch)
tree44a233c2c1aa6a4324d6ade2d0c9de48eb41a8dc
parente3272214736449a585620c00ebfd80286f3a5500 (diff)
downloadastroid-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--ChangeLog11
-rw-r--r--astroid/bases.py1
-rw-r--r--astroid/interpreter/objectmodel.py27
-rw-r--r--astroid/objects.py11
-rw-r--r--astroid/protocols.py5
-rw-r--r--astroid/tests/unittest_object_model.py42
6 files changed, 95 insertions, 2 deletions
diff --git a/ChangeLog b/ChangeLog
index ee5eb6c2..bfe867a1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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()