summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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()