summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2016-03-03 21:19:55 +0000
committerClaudiu Popa <pcmanticore@gmail.com>2016-03-03 21:19:55 +0000
commita315ef29ff51ff861cb5111d706bc17d6375f0de (patch)
treed3567dd277ece40ae2c378377d2b0afe6ebd75c8
parent9613be5921f777fc0a41269f869e20a4e7124834 (diff)
downloadastroid-git-a315ef29ff51ff861cb5111d706bc17d6375f0de.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/interpreter/objectmodel.py27
-rw-r--r--astroid/interpreter/objects.py12
-rw-r--r--astroid/interpreter/runtimeabc.py4
-rw-r--r--astroid/protocols.py4
-rw-r--r--astroid/raw_building.py2
-rw-r--r--astroid/tests/unittest_object_model.py42
7 files changed, 100 insertions, 2 deletions
diff --git a/ChangeLog b/ChangeLog
index 4b9d69c3..fe7ddd6a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -9,6 +9,17 @@ Change log for the astroid package (used to be astng)
to a method was found.
Closes issue #257
+ * 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
+
* wildcard_imported_names() got replaced by public_names()
Our understanding of wildcard imports through __all__ was
diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py
index 7c32e6fc..af1205cd 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
@@ -518,3 +519,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/interpreter/objects.py b/astroid/interpreter/objects.py
index 3c61aef4..237caad6 100644
--- a/astroid/interpreter/objects.py
+++ b/astroid/interpreter/objects.py
@@ -277,6 +277,18 @@ class Instance(BaseInstance):
node=self, index=index, context=context))
+@util.register_implementation(runtimeabc.ExceptionInstance)
+class ExceptionInstance(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())
+
+
class Method(Proxy):
def __repr__(self):
diff --git a/astroid/interpreter/runtimeabc.py b/astroid/interpreter/runtimeabc.py
index 9f140cde..8dec1089 100644
--- a/astroid/interpreter/runtimeabc.py
+++ b/astroid/interpreter/runtimeabc.py
@@ -36,6 +36,10 @@ class Instance(RuntimeObject):
"""Class representing an instance."""
+class ExceptionInstance(Instance):
+ pass
+
+
class BuiltinInstance(RuntimeObject):
"""Represents an instance of a builtin."""
diff --git a/astroid/protocols.py b/astroid/protocols.py
index a4172595..47741e33 100644
--- a/astroid/protocols.py
+++ b/astroid/protocols.py
@@ -34,6 +34,7 @@ from astroid.tree import treeabc
from astroid import util
raw_building = util.lazy_import('raw_building')
+objects = util.lazy_import('interpreter.objects')
def _reflected_name(name):
@@ -425,7 +426,8 @@ def _resolve_asspart(parts, assign_path, context):
def excepthandler_assigned_stmts(self, nodes, node=None, context=None, assign_path=None):
for assigned in inferenceutil.unpack_infer(self.type):
if isinstance(assigned, treeabc.ClassDef):
- assigned = assigned.instantiate_class()
+ assigned = objects.ExceptionInstance(assigned)
+
yield assigned
# Explicit StopIteration to return error information, see comment
diff --git a/astroid/raw_building.py b/astroid/raw_building.py
index 9da40721..265124e1 100644
--- a/astroid/raw_building.py
+++ b/astroid/raw_building.py
@@ -630,7 +630,7 @@ _NameAST = collections.namedtuple('_NameAST', 'name ast')
BUILTIN_TYPES = (types.GetSetDescriptorType,
types.MemberDescriptorType, type(None), type(NotImplemented),
types.GeneratorType, types.FunctionType, types.MethodType,
- types.BuiltinFunctionType, types.ModuleType)
+ types.BuiltinFunctionType, types.ModuleType, types.TracebackType)
def ast_from_builtins():
diff --git a/astroid/tests/unittest_object_model.py b/astroid/tests/unittest_object_model.py
index a058c998..7ed433fb 100644
--- a/astroid/tests/unittest_object_model.py
+++ b/astroid/tests/unittest_object_model.py
@@ -426,5 +426,47 @@ class GeneratorModelTest(unittest.TestCase):
self.assertIsInstance(send, astroid.FunctionDef)
+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()