diff options
author | Claudiu Popa <pcmanticore@gmail.com> | 2016-03-03 21:19:55 +0000 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2016-03-03 21:19:55 +0000 |
commit | a315ef29ff51ff861cb5111d706bc17d6375f0de (patch) | |
tree | d3567dd277ece40ae2c378377d2b0afe6ebd75c8 | |
parent | 9613be5921f777fc0a41269f869e20a4e7124834 (diff) | |
download | astroid-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-- | ChangeLog | 11 | ||||
-rw-r--r-- | astroid/interpreter/objectmodel.py | 27 | ||||
-rw-r--r-- | astroid/interpreter/objects.py | 12 | ||||
-rw-r--r-- | astroid/interpreter/runtimeabc.py | 4 | ||||
-rw-r--r-- | astroid/protocols.py | 4 | ||||
-rw-r--r-- | astroid/raw_building.py | 2 | ||||
-rw-r--r-- | astroid/tests/unittest_object_model.py | 42 |
7 files changed, 100 insertions, 2 deletions
@@ -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() |