summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCeridwen <ceridwenv@gmail.com>2015-11-02 00:10:54 -0500
committerCeridwen <ceridwenv@gmail.com>2015-11-02 00:10:54 -0500
commit1ba0f2d96fbc45ff0b6014b12db98716183e8277 (patch)
tree54b7c4d3ecad6fcda1211ea3a8e5f11f6b407287
parent83f6c45c343cae87f415268959b1056030a5e74c (diff)
downloadastroid-1ba0f2d96fbc45ff0b6014b12db98716183e8277.tar.gz
This bookmark adds structured exceptions to astroid.
Major changes: * AstroidError has an __init__ that accepts arbitrary keyword-only arguments for adding information to exceptions, and a __str__ that lazily uses exception attributes to generate a message. The first positional argument to an exception is assigned to .message. The new API should be fully backwards compatible in general. * Some exceptions are combined or renamed; the old names are still available. * The OperationErrors used by pylint are now BadOperationMessages and located in util.py. * The AstroidBuildingException in _data_build stores the SyntaxError in its .error attribute rather than args[0]. * Many places where exceptions are raised have new, hopefully more useful error messages. The only major issue remaining is how to propagate information into decorators.
-rw-r--r--astroid/arguments.py39
-rw-r--r--astroid/bases.py31
-rw-r--r--astroid/brain/brain_builtin_inference.py10
-rw-r--r--astroid/brain/brain_gi.py4
-rw-r--r--astroid/brain/brain_six.py2
-rw-r--r--astroid/builder.py23
-rw-r--r--astroid/decorators.py16
-rw-r--r--astroid/exceptions.py178
-rw-r--r--astroid/helpers.py2
-rw-r--r--astroid/inference.py65
-rw-r--r--astroid/manager.py49
-rw-r--r--astroid/mixins.py20
-rw-r--r--astroid/node_classes.py17
-rw-r--r--astroid/objects.py43
-rw-r--r--astroid/protocols.py56
-rw-r--r--astroid/scoped_nodes.py75
-rw-r--r--astroid/tests/unittest_brain.py2
-rw-r--r--astroid/tests/unittest_builder.py2
-rw-r--r--astroid/tests/unittest_lookup.py6
-rw-r--r--astroid/tests/unittest_nodes.py8
-rw-r--r--astroid/tests/unittest_objects.py20
-rw-r--r--astroid/tests/unittest_scoped_nodes.py45
-rw-r--r--astroid/util.py35
-rw-r--r--tox.ini3
24 files changed, 506 insertions, 245 deletions
diff --git a/astroid/arguments.py b/astroid/arguments.py
index 5670fa8..6483189 100644
--- a/astroid/arguments.py
+++ b/astroid/arguments.py
@@ -141,9 +141,18 @@ class CallSite(object):
return values
def infer_argument(self, funcnode, name, context):
- """infer a function argument value according to the call context"""
+ """infer a function argument value according to the call context
+
+ Arguments:
+ funcnode: The function being called.
+ name: The name of the argument whose value is being inferred.
+ context: TODO
+ """
if name in self.duplicated_keywords:
- raise exceptions.InferenceError(name)
+ raise exceptions.InferenceError('The arguments passed to {func!r} '
+ ' have duplicate keywords.',
+ call_site=self, func=funcnode,
+ arg=name, context=context)
# Look into the keywords first, maybe it's already there.
try:
@@ -154,7 +163,11 @@ class CallSite(object):
# Too many arguments given and no variable arguments.
if len(self.positional_arguments) > len(funcnode.args.args):
if not funcnode.args.vararg:
- raise exceptions.InferenceError(name)
+ raise exceptions.InferenceError('Too many positional arguments '
+ 'passed to {func!r} that does '
+ 'not have *args.',
+ call_site=self, func=funcnode,
+ arg=name, context=context)
positional = self.positional_arguments[:len(funcnode.args.args)]
vararg = self.positional_arguments[len(funcnode.args.args):]
@@ -204,7 +217,13 @@ class CallSite(object):
# It wants all the keywords that were passed into
# the call site.
if self.has_invalid_keywords():
- raise exceptions.InferenceError
+ raise exceptions.InferenceError(
+ "Inference failed to find values for all keyword arguments "
+ "to {func!r}: {unpacked_kwargs!r} doesn't correspond to "
+ "{keyword_arguments!r}.",
+ keyword_arguments=self.keyword_arguments,
+ unpacked_kwargs = self._unpacked_kwargs,
+ call_site=self, func=funcnode, arg=name, context=context)
kwarg = nodes.Dict(lineno=funcnode.args.lineno,
col_offset=funcnode.args.col_offset,
parent=funcnode.args)
@@ -215,7 +234,13 @@ class CallSite(object):
# It wants all the args that were passed into
# the call site.
if self.has_invalid_arguments():
- raise exceptions.InferenceError
+ raise exceptions.InferenceError(
+ "Inference failed to find values for all positional "
+ "arguments to {func!r}: {unpacked_args!r} doesn't "
+ "correspond to {positional_arguments!r}.",
+ positional_arguments=self.positional_arguments,
+ unpacked_args=self._unpacked_args,
+ call_site=self, func=funcnode, arg=name, context=context)
args = nodes.Tuple(lineno=funcnode.args.lineno,
col_offset=funcnode.args.col_offset,
parent=funcnode.args)
@@ -227,4 +252,6 @@ class CallSite(object):
return funcnode.args.default_value(name).infer(context)
except exceptions.NoDefault:
pass
- raise exceptions.InferenceError(name)
+ raise exceptions.InferenceError('No value found for argument {name} to '
+ '{func!r}', call_site=self,
+ func=funcnode, arg=name, context=context)
diff --git a/astroid/bases.py b/astroid/bases.py
index 4b7f83b..15cf579 100644
--- a/astroid/bases.py
+++ b/astroid/bases.py
@@ -98,13 +98,15 @@ def _infer_stmts(stmts, context, frame=None):
for inferred in stmt.infer(context=context):
yield inferred
inferred = True
- except exceptions.UnresolvableName:
+ except exceptions.NameInferenceError:
continue
except exceptions.InferenceError:
yield util.YES
inferred = True
if not inferred:
- raise exceptions.InferenceError(str(stmt))
+ raise exceptions.InferenceError(
+ 'Inference failed for all members of {stmts!r}.',
+ stmts=stmts, frame=frame, context=context)
def _infer_method_result_truth(instance, method_name, context):
@@ -129,7 +131,7 @@ class Instance(Proxy):
def getattr(self, name, context=None, lookupclass=True):
try:
values = self._proxied.instance_attr(name, context)
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError as exception:
if name == '__class__':
return [self._proxied]
if lookupclass:
@@ -139,14 +141,16 @@ class Instance(Proxy):
return self._proxied.local_attr(name)
return self._proxied.getattr(name, context,
class_context=False)
- util.reraise(exceptions.NotFoundError(name))
+ util.reraise(exceptions.AttributeInferenceError(target=self,
+ attribute=name,
+ context=context))
# since we've no context information, return matching class members as
# well
if lookupclass:
try:
return values + self._proxied.getattr(name, context,
class_context=False)
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
pass
return values
@@ -164,15 +168,15 @@ class Instance(Proxy):
for stmt in _infer_stmts(self._wrap_attr(get_attr, context),
context, frame=self):
yield stmt
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
try:
- # fallback to class'igetattr since it has some logic to handle
+ # fallback to class.igetattr since it has some logic to handle
# descriptors
for stmt in self._wrap_attr(self._proxied.igetattr(name, context),
context):
yield stmt
- except exceptions.NotFoundError:
- util.reraise(exceptions.InferenceError(name))
+ except exceptions.AttributeInferenceError as error:
+ util.reraise(exceptions.InferenceError(**vars(error)))
def _wrap_attr(self, attrs, context=None):
"""wrap bound methods of attrs in a InstanceMethod proxies"""
@@ -207,7 +211,8 @@ class Instance(Proxy):
inferred = True
yield res
if not inferred:
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError(node=self, caller=caller,
+ context=context)
def __repr__(self):
return '<Instance of %s.%s at 0x%s>' % (self._proxied.root().name,
@@ -221,7 +226,7 @@ class Instance(Proxy):
try:
self._proxied.getattr('__call__', class_context=False)
return True
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
return False
def pytype(self):
@@ -248,11 +253,11 @@ class Instance(Proxy):
try:
result = _infer_method_result_truth(self, BOOL_SPECIAL_METHOD, context)
- except (exceptions.InferenceError, exceptions.NotFoundError):
+ except (exceptions.InferenceError, exceptions.AttributeInferenceError):
# Fallback to __len__.
try:
result = _infer_method_result_truth(self, '__len__', context)
- except (exceptions.NotFoundError, exceptions.InferenceError):
+ except (exceptions.AttributeInferenceError, exceptions.InferenceError):
return True
return result
diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py
index c6245be..eb61b70 100644
--- a/astroid/brain/brain_builtin_inference.py
+++ b/astroid/brain/brain_builtin_inference.py
@@ -5,8 +5,8 @@ import sys
from textwrap import dedent
import six
-from astroid import (MANAGER, UseInferenceDefault, NotFoundError,
- inference_tip, InferenceError, UnresolvableName)
+from astroid import (MANAGER, UseInferenceDefault, AttributeInferenceError,
+ inference_tip, InferenceError, NameInferenceError)
from astroid import arguments
from astroid.builder import AstroidBuilder
from astroid import helpers
@@ -193,7 +193,7 @@ def _get_elts(arg, context):
(nodes.List, nodes.Tuple, nodes.Set))
try:
inferred = next(arg.infer(context))
- except (InferenceError, UnresolvableName):
+ except (InferenceError, NameInferenceError):
raise UseInferenceDefault()
if isinstance(inferred, nodes.Dict):
items = inferred.items
@@ -356,7 +356,7 @@ def infer_getattr(node, context=None):
try:
return next(obj.igetattr(attr, context=context))
- except (StopIteration, InferenceError, NotFoundError):
+ except (StopIteration, InferenceError, AttributeInferenceError):
if len(node.args) == 3:
# Try to infer the default and return it instead.
try:
@@ -384,7 +384,7 @@ def infer_hasattr(node, context=None):
except UseInferenceDefault:
# Can't infer something from this function call.
return util.YES
- except NotFoundError:
+ except AttributeInferenceError:
# Doesn't have it.
return nodes.Const(False)
return nodes.Const(True)
diff --git a/astroid/brain/brain_gi.py b/astroid/brain/brain_gi.py
index f8acb42..0860207 100644
--- a/astroid/brain/brain_gi.py
+++ b/astroid/brain/brain_gi.py
@@ -114,7 +114,7 @@ def _gi_build_stub(parent):
def _import_gi_module(modname):
# we only consider gi.repository submodules
if not modname.startswith('gi.repository.'):
- raise AstroidBuildingException()
+ raise AstroidBuildingException(modname=modname)
# build astroid representation unless we already tried so
if modname not in _inspected_modules:
modnames = [modname]
@@ -155,7 +155,7 @@ def _import_gi_module(modname):
else:
astng = _inspected_modules[modname]
if astng is None:
- raise AstroidBuildingException('Failed to import module %r' % modname)
+ raise AstroidBuildingException(modname=modname)
return astng
def _looks_like_require_version(node):
diff --git a/astroid/brain/brain_six.py b/astroid/brain/brain_six.py
index a1043ea..3b2b945 100644
--- a/astroid/brain/brain_six.py
+++ b/astroid/brain/brain_six.py
@@ -254,7 +254,7 @@ def six_moves_transform():
def _six_fail_hook(modname):
if modname != 'six.moves':
- raise AstroidBuildingException
+ raise AstroidBuildingException(modname=modname)
module = AstroidBuilder(MANAGER).string_build(_IMPORTS)
module.name = 'six.moves'
return module
diff --git a/astroid/builder.py b/astroid/builder.py
index 9bb78b1..4db4051 100644
--- a/astroid/builder.py
+++ b/astroid/builder.py
@@ -51,8 +51,9 @@ if sys.version_info >= (3, 0):
data = stream.read()
except UnicodeError: # wrong encoding
# detect_encoding returns utf-8 if no encoding specified
- msg = 'Wrong (%s) or no encoding specified' % encoding
- util.reraise(exceptions.AstroidBuildingException(msg))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Wrong ({encoding}) or no encoding specified for {filename}.',
+ encoding=encoding, filename=filename))
return stream, encoding, data
else:
@@ -123,11 +124,13 @@ class AstroidBuilder(raw_building.InspectBuilder):
try:
stream, encoding, data = open_source_file(path)
except IOError as exc:
- msg = 'Unable to load file %r (%s)' % (path, exc)
- util.reraise(exceptions.AstroidBuildingException(msg))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Unable to load file {path}:\n{error}',
+ modname=modname, path=path, error=exc))
except (SyntaxError, LookupError) as exc:
- # Python 3 encoding specification error or unknown encoding
- util.reraise(exceptions.AstroidBuildingException(*exc.args))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Python 3 encoding specification error or unknown encoding:\n'
+ '{error}', modname=modname, path=path, error=exc))
with stream:
# get module name if necessary
if modname is None:
@@ -169,12 +172,16 @@ class AstroidBuilder(raw_building.InspectBuilder):
try:
node = _parse(data + '\n')
except (TypeError, ValueError) as exc:
- util.reraise(exceptions.AstroidBuildingException(*exc.args))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Parsing Python code failed:\n{error}',
+ source=data, modname=modname, path=path, error=exc))
except SyntaxError as exc:
# Pass the entire exception object to AstroidBuildingException,
# since pylint uses this as an introspection method,
# in order to find what error happened.
- util.reraise(exceptions.AstroidBuildingException(exc))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Syntax error in Python source: {error}',
+ source=data, modname=modname, path=path, error=exc))
if path is not None:
node_file = os.path.abspath(path)
else:
diff --git a/astroid/decorators.py b/astroid/decorators.py
index 0709744..27ce983 100644
--- a/astroid/decorators.py
+++ b/astroid/decorators.py
@@ -117,9 +117,17 @@ def yes_if_nothing_inferred(func, instance, args, kwargs):
@wrapt.decorator
def raise_if_nothing_inferred(func, instance, args, kwargs):
+ '''All generators wrapped with raise_if_nothing_inferred *must* raise
+ exceptions.DefaultStop if they can terminate without output, to
+ propagate error information.
+ '''
inferred = False
- for node in func(*args, **kwargs):
- inferred = True
- yield node
+ fields = {}
+ try:
+ for node in func(*args, **kwargs):
+ inferred = True
+ yield node
+ except exceptions.DefaultStop as e:
+ fields = vars(e)
if not inferred:
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError(**fields)
diff --git a/astroid/exceptions.py b/astroid/exceptions.py
index b308258..9f49753 100644
--- a/astroid/exceptions.py
+++ b/astroid/exceptions.py
@@ -16,88 +16,176 @@
# You should have received a copy of the GNU Lesser General Public License along
# with astroid. If not, see <http://www.gnu.org/licenses/>.
"""this module contains exceptions used in the astroid library
-
"""
+from astroid import util
+
class AstroidError(Exception):
- """base exception class for all astroid related exceptions"""
+ """base exception class for all astroid related exceptions
+
+ AstroidError and its subclasses are structured, intended to hold
+ objects representing state when the exception is thrown. Field
+ values are passed to the constructor as keyword-only arguments.
+ Each subclass has its own set of standard fields, but use your
+ best judgment to decide whether a specific exception instance
+ needs more or fewer fields for debugging. Field values may be
+ used to lazily generate the error message: self.message.format()
+ will be called with the field names and values supplied as keyword
+ arguments.
+ """
+ def __init__(self, message='', **kws):
+ self.message = message
+ for key, value in kws.items():
+ setattr(self, key, value)
+
+ def __str__(self):
+ return self.message.format(**vars(self))
+
class AstroidBuildingException(AstroidError):
- """exception class when we are unable to build an astroid representation"""
+ """exception class when we are unable to build an astroid representation
+
+ Standard attributes:
+ modname: Name of the module that AST construction failed for.
+ error: Exception raised during construction.
+ """
+
+ def __init__(self, message='Failed to import module {modname}.', **kws):
+ super(AstroidBuildingException, self).__init__(message, **kws)
+
+
+class NoDefault(AstroidError):
+ """raised by function's `default_value` method when an argument has
+ no default value
+
+ Standard attributes:
+ func: Function node.
+ name: Name of argument without a default.
+ """
+ func = None
+ name = None
+
+ def __init__(self, message='{func!r} has no default for {name!r}.', **kws):
+ super(NoDefault, self).__init__(message, **kws)
+
+
+class DefaultStop(AstroidError):
+ '''This is a special error that's only meant to be raised in
+ generators wrapped with raise_if_nothing_inferred and
+ yes_if_nothing_inferred. It does nothing other than carry a set
+ of attributes to be used in raising in InferenceError.
+
+ '''
+
+ # def __init__(self, message='{func!r} has no default for {name!r}.', **kws):
+ # super(NoDefault, self).__init__(message, **kws)
+
class ResolveError(AstroidError):
- """base class of astroid resolution/inference error"""
+ """Base class of astroid resolution/inference error.
+
+ ResolveError is not intended to be raised.
+
+ Standard attributes:
+ context: InferenceContext object.
+ """
+ context = None
+
class MroError(ResolveError):
- """Error raised when there is a problem with method resolution of a class."""
+ """Error raised when there is a problem with method resolution of a class.
+ Standard attributes:
+ mros: A sequence of sequences containing ClassDef nodes.
+ cls: ClassDef node whose MRO resolution failed.
+ context: InferenceContext object.
+ """
+ mros = ()
+ cls = None
+
+ def __str__(self):
+ mro_names = ", ".join("({})".format(", ".join(b.name for b in m))
+ for m in self.mros)
+ return self.message.format(mros=mro_names, cls=self.cls)
class DuplicateBasesError(MroError):
"""Error raised when there are duplicate bases in the same class bases."""
-
class InconsistentMroError(MroError):
"""Error raised when a class's MRO is inconsistent."""
class SuperError(ResolveError):
- """Error raised when there is a problem with a super call."""
+ """Error raised when there is a problem with a super call.
-class SuperArgumentTypeError(SuperError):
- """Error raised when the super arguments are invalid."""
+ Standard attributes:
+ super_: The Super instance that raised the exception.
+ context: InferenceContext object.
+ """
+ super_ = None
+ def __str__(self):
+ return self.message.format(**vars(self.super_))
-class NotFoundError(ResolveError):
- """raised when we are unable to resolve a name"""
class InferenceError(ResolveError):
- """raised when we are unable to infer a node"""
+ """raised when we are unable to infer a node
-class UseInferenceDefault(Exception):
- """exception to be raised in custom inference function to indicate that it
- should go back to the default behaviour
+ Standard attributes:
+ node: The node inference was called on.
+ context: InferenceContext object.
"""
+ node = None
+ context= None
-class UnresolvableName(InferenceError):
- """raised when we are unable to resolve a name"""
-
-class NoDefault(AstroidError):
- """raised by function's `default_value` method when an argument has
- no default value
- """
+ def __init__(self, message='Inference failed for {node!r}.', **kws):
+ super(InferenceError, self).__init__(message, **kws)
-class OperationError(object):
- """Object which describes a TypeError occurred somewhere in the inference chain
+# Why does this inherit from InferenceError rather than ResolveError?
+# Changing it causes some inference tests to fail.
+class NameInferenceError(InferenceError):
+ """Raised when a name lookup fails, corresponds to NameError.
- This is not an exception, but a container object which holds the types and
- the error which occurred.
+ Standard attributes:
+ name: The name for which lookup failed, as a string.
+ scope: The node representing the scope in which the lookup occurred.
+ context: InferenceContext object.
"""
+ name = None
+ scope = None
+ def __init__(self, message='{name!r} not found in {scope!r}.', **kws):
+ super(NameInferenceError, self).__init__(message, **kws)
-class UnaryOperationError(OperationError):
- """Object which describes operational failures on UnaryOps."""
- def __init__(self, operand, op, error):
- self.operand = operand
- self.op = op
- self.error = error
+class AttributeInferenceError(ResolveError):
+ """Raised when an attribute lookup fails, corresponds to AttributeError.
- def __str__(self):
- operand_type = self.operand.name
- msg = "bad operand type for unary {}: {}"
- return msg.format(self.op, operand_type)
+ Standard attributes:
+ target: The node for which lookup failed.
+ attribute: The attribute for which lookup failed, as a string.
+ context: InferenceContext object.
+ """
+ target = None
+ attribute = None
+ def __init__(self, message='{attribute!r} not found on {target!r}.', **kws):
+ super(AttributeInferenceError, self).__init__(message, **kws)
-class BinaryOperationError(OperationError):
- """Object which describes type errors for BinOps."""
- def __init__(self, left_type, op, right_type):
- self.left_type = left_type
- self.right_type = right_type
- self.op = op
+class UseInferenceDefault(Exception):
+ """exception to be raised in custom inference function to indicate that it
+ should go back to the default behaviour
+ """
- def __str__(self):
- msg = "unsupported operand type(s) for {}: {!r} and {!r}"
- return msg.format(self.op, self.left_type.name, self.right_type.name)
+
+# Backwards-compatibility aliases
+OperationError = util.BadOperationMessage
+UnaryOperationError = util.BadUnaryOperationMessage
+BinaryOperationError = util.BadBinaryOperationMessage
+
+SuperArgumentTypeError = SuperError
+UnresolvableName = NameInferenceError
+NotFoundError = AttributeInferenceError
diff --git a/astroid/helpers.py b/astroid/helpers.py
index 00f4784..d4cd0dd 100644
--- a/astroid/helpers.py
+++ b/astroid/helpers.py
@@ -86,7 +86,7 @@ def object_type(node, context=None):
This is used to implement the ``type`` builtin, which means that it's
used for inferring type calls, as well as used in a couple of other places
- in the inference.
+ in the inference.
The node will be inferred first, so this function can support all
sorts of objects, as long as they support inference.
"""
diff --git a/astroid/inference.py b/astroid/inference.py
index 2943596..b5e45df 100644
--- a/astroid/inference.py
+++ b/astroid/inference.py
@@ -89,7 +89,9 @@ def infer_name(self, context=None):
_, stmts = parent_function.lookup(self.name)
if not stmts:
- raise exceptions.UnresolvableName(self.name)
+ raise exceptions.NameInferenceError(name=self.name,
+ scope=self.scope(),
+ context=context)
context = context.clone()
context.lookupname = self.name
return bases._infer_stmts(stmts, context, frame)
@@ -116,6 +118,7 @@ def infer_call(self, context=None):
except exceptions.InferenceError:
## XXX log error ?
continue
+ raise exceptions.DefaultStop(node=self, context=context)
nodes.Call._infer = infer_call
@@ -124,7 +127,7 @@ def infer_import(self, context=None, asname=True):
"""infer an Import node: return the imported module/object"""
name = context.lookupname
if name is None:
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError(node=self, context=context)
if asname:
yield self.do_import_module(self.real_name(name))
else:
@@ -144,7 +147,7 @@ def infer_import_from(self, context=None, asname=True):
"""infer a ImportFrom node: return the imported module/object"""
name = context.lookupname
if name is None:
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError(node=self, context=context)
if asname:
name = self.real_name(name)
module = self.do_import_module()
@@ -153,8 +156,9 @@ def infer_import_from(self, context=None, asname=True):
context.lookupname = name
stmts = module.getattr(name, ignore_locals=module is self.root())
return bases._infer_stmts(stmts, context)
- except exceptions.NotFoundError:
- util.reraise(exceptions.InferenceError(name))
+ except exceptions.AttributeInferenceError as error:
+ util.reraise(exceptions.InferenceError(
+ error.message, target=self, attribute=name, context=context))
nodes.ImportFrom._infer = infer_import_from
@@ -170,11 +174,12 @@ def infer_attribute(self, context=None):
for obj in owner.igetattr(self.attrname, context):
yield obj
context.boundnode = None
- except (exceptions.NotFoundError, exceptions.InferenceError):
+ except (exceptions.AttributeInferenceError, exceptions.InferenceError):
context.boundnode = None
except AttributeError:
# XXX method / function
context.boundnode = None
+ raise exceptions.DefaultStop(node=self, context=context)
nodes.Attribute._infer = decorators.path_wrapper(infer_attribute)
nodes.AssignAttr.infer_lhs = infer_attribute # # won't work with a path wrapper
@@ -182,12 +187,13 @@ nodes.AssignAttr.infer_lhs = infer_attribute # # won't work with a path wrapper
@decorators.path_wrapper
def infer_global(self, context=None):
if context.lookupname is None:
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError(node=self, context=context)
try:
return bases._infer_stmts(self.root().getattr(context.lookupname),
context)
- except exceptions.NotFoundError:
- util.reraise(exceptions.InferenceError())
+ except exceptions.AttributeInferenceError as error:
+ util.reraise(exceptions.InferenceError(
+ error.message, target=self, attribute=name, context=context))
nodes.Global._infer = infer_global
@@ -257,15 +263,16 @@ def infer_subscript(self, context=None):
if index:
index_value = index.value
else:
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError(node=self, context=context)
if index_value is _SLICE_SENTINEL:
- raise exceptions.InferenceError
+ raise exceptions.InferenceError(node=self, context=context)
try:
assigned = value.getitem(index_value, context)
except (IndexError, TypeError, AttributeError) as exc:
- util.reraise(exceptions.InferenceError(*exc.args))
+ util.reraise(exceptions.InferenceError(node=self, error=exc,
+ context=context))
# Prevent inferring if the inferred subscript
# is the same as the original subscripted object.
@@ -275,6 +282,8 @@ def infer_subscript(self, context=None):
for inferred in assigned.infer(context):
yield inferred
+ raise exceptions.DefaultStop(node=self, context=context)
+
nodes.Subscript._infer = decorators.path_wrapper(infer_subscript)
nodes.Subscript.infer_lhs = infer_subscript
@@ -329,6 +338,8 @@ def _infer_boolop(self, context=None):
else:
yield value
+ raise exceptions.DefaultStop(node=self, context=context)
+
nodes.BoolOp._infer = _infer_boolop
@@ -352,7 +363,7 @@ def _infer_unaryop(self, context=None):
yield operand.infer_unary_op(self.op)
except TypeError as exc:
# The operand doesn't support this operation.
- yield exceptions.UnaryOperationError(operand, self.op, exc)
+ yield util.BadUnaryOperationMessage(operand, self.op, exc)
except AttributeError as exc:
meth = protocols.UNARY_OP_METHOD[self.op]
if meth is None:
@@ -368,7 +379,7 @@ def _infer_unaryop(self, context=None):
if not isinstance(operand, bases.Instance):
# The operation was used on something which
# doesn't support it.
- yield exceptions.UnaryOperationError(operand, self.op, exc)
+ yield util.BadUnaryOperationMessage(operand, self.op, exc)
continue
try:
@@ -386,9 +397,9 @@ def _infer_unaryop(self, context=None):
yield operand
else:
yield result
- except exceptions.NotFoundError as exc:
+ except exceptions.AttributeInferenceError as exc:
# The unary operation special method was not found.
- yield exceptions.UnaryOperationError(operand, self.op, exc)
+ yield util.BadUnaryOperationMessage(operand, self.op, exc)
except exceptions.InferenceError:
yield util.YES
@@ -397,8 +408,10 @@ def _infer_unaryop(self, context=None):
@decorators.path_wrapper
def infer_unaryop(self, context=None):
"""Infer what an UnaryOp should return when evaluated."""
- return _filter_operation_errors(self, _infer_unaryop, context,
- exceptions.UnaryOperationError)
+ for inferred in _filter_operation_errors(self, _infer_unaryop, context,
+ util.BadUnaryOperationMessage):
+ yield inferred
+ raise exceptions.DefaultStop(node=self, context=context)
nodes.UnaryOp._infer_unaryop = _infer_unaryop
nodes.UnaryOp._infer = infer_unaryop
@@ -544,7 +557,7 @@ def _infer_binary_operation(left, right, op, context, flow_factory):
results = list(method())
except AttributeError:
continue
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
continue
except exceptions.InferenceError:
yield util.YES
@@ -569,9 +582,9 @@ def _infer_binary_operation(left, right, op, context, flow_factory):
for result in results:
yield result
return
- # TODO(cpopa): yield a BinaryOperationError here,
+ # TODO(cpopa): yield a BadBinaryOperationMessage here,
# since the operation is not supported
- yield exceptions.BinaryOperationError(left_type, op, right_type)
+ yield util.BadBinaryOperationMessage(left_type, op, right_type)
def _infer_binop(self, context):
@@ -610,7 +623,7 @@ def _infer_binop(self, context):
@decorators.path_wrapper
def infer_binop(self, context=None):
return _filter_operation_errors(self, _infer_binop, context,
- exceptions.BinaryOperationError)
+ util.BadBinaryOperationMessage)
nodes.BinOp._infer_binop = _infer_binop
nodes.BinOp._infer = infer_binop
@@ -649,7 +662,7 @@ def _infer_augassign(self, context=None):
@decorators.path_wrapper
def infer_augassign(self, context=None):
return _filter_operation_errors(self, _infer_augassign, context,
- exceptions.BinaryOperationError)
+ util.BadBinaryOperationMessage)
nodes.AugAssign._infer_augassign = _infer_augassign
nodes.AugAssign._infer = infer_augassign
@@ -660,7 +673,7 @@ nodes.AugAssign._infer = infer_augassign
def infer_arguments(self, context=None):
name = context.lookupname
if name is None:
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError(node=self, context=context)
return protocols._arguments_infer_argname(self, name, context)
nodes.Arguments._infer = infer_arguments
@@ -715,11 +728,11 @@ def instance_getitem(self, index, context=None):
method = next(self.igetattr('__getitem__', context=context))
if not isinstance(method, bases.BoundMethod):
- raise exceptions.InferenceError
+ raise exceptions.InferenceError(node=self, context=context)
try:
return next(method.infer_call_result(self, new_context))
except StopIteration:
- util.reraise(exceptions.InferenceError())
+ util.reraise(exceptions.InferenceError(node=self, context=context))
bases.Instance.getitem = instance_getitem
diff --git a/astroid/manager.py b/astroid/manager.py
index 07d7543..c14125f 100644
--- a/astroid/manager.py
+++ b/astroid/manager.py
@@ -90,7 +90,7 @@ class AstroidManager(object):
elif fallback and modname:
return self.ast_from_module_name(modname)
raise exceptions.AstroidBuildingException(
- 'unable to get astroid for file %s' % filepath)
+ 'Unable to build an AST for {path}.', path=filepath)
def _build_stub_module(self, modname):
from astroid.builder import AstroidBuilder
@@ -127,15 +127,18 @@ class AstroidManager(object):
try:
module = modutils.load_module_from_name(modname)
except Exception as ex: # pylint: disable=broad-except
- msg = 'Unable to load module %s (%s)' % (modname, ex)
- util.reraise(exceptions.AstroidBuildingException(msg))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Loading {modname} failed with:\n{error}',
+ modname=modname, path=filepath, error=ex))
return self.ast_from_module(module, modname)
elif mp_type == imp.PY_COMPILED:
- msg = "Unable to load compiled module %s" % (modname,)
- raise exceptions.AstroidBuildingException(msg)
+ raise exceptions.AstroidBuildingException(
+ "Unable to load compiled module {modname}.",
+ modname=modname, path=filepath)
if filepath is None:
- msg = "Unable to load module %s" % (modname,)
- raise exceptions.AstroidBuildingException(msg)
+ raise exceptions.AstroidBuildingException(
+ "Can't find a file for module {modname}.",
+ modname=modname)
return self.ast_from_file(filepath, modname, fallback=False)
except exceptions.AstroidBuildingException as e:
for hook in self._failed_import_hooks:
@@ -179,8 +182,9 @@ class AstroidManager(object):
modname.split('.'), context_file=contextfile)
traceback = sys.exc_info()[2]
except ImportError as ex:
- msg = 'Unable to load module %s (%s)' % (modname, ex)
- value = exceptions.AstroidBuildingException(msg)
+ value = exceptions.AstroidBuildingException(
+ 'Failed to import module {modname} with error:\n{error}.',
+ modname=modname, error=ex)
traceback = sys.exc_info()[2]
self._mod_file_cache[(modname, contextfile)] = value
if isinstance(value, exceptions.AstroidBuildingException):
@@ -209,8 +213,9 @@ class AstroidManager(object):
try:
modname = klass.__module__
except AttributeError:
- msg = 'Unable to get module for class %s' % safe_repr(klass)
- util.reraise(exceptions.AstroidBuildingException(msg))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Unable to get module for class {class_name}.',
+ cls=klass, class_repr=safe_repr(klass), modname=modname))
modastroid = self.ast_from_module_name(modname)
return modastroid.getattr(klass.__name__)[0] # XXX
@@ -223,21 +228,23 @@ class AstroidManager(object):
try:
modname = klass.__module__
except AttributeError:
- msg = 'Unable to get module for %s' % safe_repr(klass)
- util.reraise(exceptions.AstroidBuildingException(msg))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Unable to get module for {class_repr}.',
+ cls=klass, class_repr=safe_repr(klass)))
except Exception as ex: # pylint: disable=broad-except
- msg = ('Unexpected error while retrieving module for %s: %s'
- % (safe_repr(klass), ex))
- util.reraise(exceptions.AstroidBuildingException(msg))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Unexpected error while retrieving module for {class_repr}:\n'
+ '{error}', cls=klass, class_repr=safe_repr(klass), error=ex))
try:
name = klass.__name__
except AttributeError:
- msg = 'Unable to get name for %s' % safe_repr(klass)
- util.reraise(exceptions.AstroidBuildingException(msg))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Unable to get name for {class_repr}:\n',
+ cls=klass, class_repr=safe_repr(klass)))
except Exception as ex: # pylint: disable=broad-except
- exc = ('Unexpected error while retrieving name for %s: %s'
- % (safe_repr(klass), ex))
- util.reraise(exceptions.AstroidBuildingException(exc))
+ util.reraise(exceptions.AstroidBuildingException(
+ 'Unexpected error while retrieving name for {class_repr}:\n'
+ '{error}', cls=klass, class_repr=safe_repr(klass), error=ex))
# take care, on living object __module__ is regularly wrong :(
modastroid = self.ast_from_module_name(modname)
if klass is obj:
diff --git a/astroid/mixins.py b/astroid/mixins.py
index 9f5f953..6ee7b4d 100644
--- a/astroid/mixins.py
+++ b/astroid/mixins.py
@@ -129,11 +129,19 @@ class ImportFromMixin(FilterStmtsMixin):
return mymodule.import_module(modname, level=level,
relative_only=level and level >= 1)
except exceptions.AstroidBuildingException as ex:
- if isinstance(ex.args[0], SyntaxError):
- util.reraise(exceptions.InferenceError(str(ex)))
- util.reraise(exceptions.InferenceError(modname))
+ if isinstance(getattr(ex, 'error', None), SyntaxError):
+ util.reraise(exceptions.InferenceError(
+ 'Could not import {modname} because of SyntaxError:\n'
+ '{syntax_error}', modname=modname, syntax_error=ex.error,
+ import_node=self))
+ util.reraise(exceptions.InferenceError('Could not import {modname}.',
+ modname=modname,
+ import_node=self))
except SyntaxError as ex:
- util.reraise(exceptions.InferenceError(str(ex)))
+ util.reraise(exceptions.InferenceError(
+ 'Could not import {modname} because of SyntaxError:\n'
+ '{syntax_error}', modname=modname, syntax_error=ex,
+ import_node=self))
def real_name(self, asname):
"""get name from 'as' name"""
@@ -145,4 +153,6 @@ class ImportFromMixin(FilterStmtsMixin):
_asname = name
if asname == _asname:
return name
- raise exceptions.NotFoundError(asname)
+ raise exceptions.AttributeInferenceError(
+ 'Could not find original name for {attribute} in {target!r}',
+ target=self, attribute=asname)
diff --git a/astroid/node_classes.py b/astroid/node_classes.py
index 7da41f5..5a92210 100644
--- a/astroid/node_classes.py
+++ b/astroid/node_classes.py
@@ -412,7 +412,8 @@ class NodeNG(object):
def _infer(self, context=None):
"""we don't know how to resolve a statement by default"""
# this method is overridden by most concrete classes
- raise exceptions.InferenceError(self.__class__.__name__)
+ raise exceptions.InferenceError('No inference function for {node!r}.',
+ node=self, context=context)
def inferred(self):
'''return list of inferred values for a more simple inference usage'''
@@ -894,7 +895,7 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
i = _find_arg(argname, self.kwonlyargs)[0]
if i is not None and self.kw_defaults[i] is not None:
return self.kw_defaults[i]
- raise exceptions.NoDefault()
+ raise exceptions.NoDefault(func=self.parent, name=argname)
def is_argument(self, name):
"""return True if the name is defined in arguments"""
@@ -1011,13 +1012,13 @@ class AugAssign(mixins.AssignTypeMixin, Statement):
def type_errors(self, context=None):
"""Return a list of TypeErrors which can occur during inference.
- Each TypeError is represented by a :class:`BinaryOperationError`,
+ Each TypeError is represented by a :class:`BadBinaryOperationMessage`,
which holds the original exception.
"""
try:
results = self._infer_augassign(context=context)
return [result for result in results
- if isinstance(result, exceptions.BinaryOperationError)]
+ if isinstance(result, util.BadBinaryOperationMessage)]
except exceptions.InferenceError:
return []
@@ -1053,13 +1054,13 @@ class BinOp(NodeNG):
def type_errors(self, context=None):
"""Return a list of TypeErrors which can occur during inference.
- Each TypeError is represented by a :class:`BinaryOperationError`,
+ Each TypeError is represented by a :class:`BadBinaryOperationMessage`,
which holds the original exception.
"""
try:
results = self._infer_binop(context=context)
return [result for result in results
- if isinstance(result, exceptions.BinaryOperationError)]
+ if isinstance(result, util.BadBinaryOperationMessage)]
except exceptions.InferenceError:
return []
@@ -1745,13 +1746,13 @@ class UnaryOp(NodeNG):
def type_errors(self, context=None):
"""Return a list of TypeErrors which can occur during inference.
- Each TypeError is represented by a :class:`UnaryOperationError`,
+ Each TypeError is represented by a :class:`BadUnaryOperationMessage`,
which holds the original exception.
"""
try:
results = self._infer_unaryop(context=context)
return [result for result in results
- if isinstance(result, exceptions.UnaryOperationError)]
+ if isinstance(result, util.BadUnaryOperationMessage)]
except exceptions.InferenceError:
return []
diff --git a/astroid/objects.py b/astroid/objects.py
index 3ab0a65..c880a4d 100644
--- a/astroid/objects.py
+++ b/astroid/objects.py
@@ -86,8 +86,9 @@ class Super(node_classes.NodeNG):
def super_mro(self):
"""Get the MRO which will be used to lookup attributes in this super."""
if not isinstance(self.mro_pointer, scoped_nodes.ClassDef):
- raise exceptions.SuperArgumentTypeError(
- "The first super argument must be type.")
+ raise exceptions.SuperError(
+ "The first argument to super must be a subtype of "
+ "type, not {mro_pointer}.", super_=self)
if isinstance(self.type, scoped_nodes.ClassDef):
# `super(type, type)`, most likely in a class method.
@@ -96,18 +97,20 @@ class Super(node_classes.NodeNG):
else:
mro_type = getattr(self.type, '_proxied', None)
if not isinstance(mro_type, (bases.Instance, scoped_nodes.ClassDef)):
- raise exceptions.SuperArgumentTypeError(
- "super(type, obj): obj must be an "
- "instance or subtype of type")
+ raise exceptions.SuperError(
+ "The second argument to super must be an "
+ "instance or subtype of type, not {type}.",
+ super_=self)
if not mro_type.newstyle:
- raise exceptions.SuperError("Unable to call super on old-style classes.")
+ raise exceptions.SuperError("Unable to call super on old-style classes.", super_=self)
mro = mro_type.mro()
if self.mro_pointer not in mro:
- raise exceptions.SuperArgumentTypeError(
- "super(type, obj): obj must be an "
- "instance or subtype of type")
+ raise exceptions.SuperError(
+ "The second argument to super must be an "
+ "instance or subtype of type, not {type}.",
+ super_=self)
index = mro.index(self.mro_pointer)
return mro[index + 1:]
@@ -138,11 +141,19 @@ class Super(node_classes.NodeNG):
try:
mro = self.super_mro()
- except (exceptions.MroError, exceptions.SuperError) as exc:
- # Don't let invalid MROs or invalid super calls
- # to leak out as is from this function.
- util.reraise(exceptions.NotFoundError(*exc.args))
-
+ # Don't let invalid MROs or invalid super calls
+ # leak out as is from this function.
+ except exceptions.SuperError as exc:
+ util.reraise(exceptions.AttributeInferenceError(
+ ('Lookup for {name} on {target!r} because super call {super!r} '
+ 'is invalid.'),
+ target=self, attribute=name, context=context, super_=exc.super_))
+ except exceptions.MroError as exc:
+ util.reraise(exceptions.AttributeInferenceError(
+ ('Lookup for {name} on {target!r} failed because {cls!r} has an '
+ 'invalid MRO.'),
+ target=self, attribute=name, context=context, mros=exc.mros,
+ cls=exc.cls))
found = False
for cls in mro:
if name not in cls.locals:
@@ -166,7 +177,9 @@ class Super(node_classes.NodeNG):
yield bases.BoundMethod(inferred, cls)
if not found:
- raise exceptions.NotFoundError(name)
+ raise exceptions.AttributeInferenceError(target=self,
+ attribute=name,
+ context=context)
def getattr(self, name, context=None):
return list(self.igetattr(name, context=context))
diff --git a/astroid/protocols.py b/astroid/protocols.py
index 7c9e4b9..2e01124 100644
--- a/astroid/protocols.py
+++ b/astroid/protocols.py
@@ -238,7 +238,6 @@ def _resolve_looppart(parts, asspath, context):
except exceptions.InferenceError:
break
-
@decorators.raise_if_nothing_inferred
def for_assigned_stmts(self, node, context=None, asspath=None):
if asspath is None:
@@ -250,6 +249,9 @@ def for_assigned_stmts(self, node, context=None, asspath=None):
for inferred in _resolve_looppart(self.iter.infer(context),
asspath, context):
yield inferred
+ raise exceptions.DefaultStop(node=self, unknown=node,
+ assign_path=asspath, context=context)
+
nodes.For.assigned_stmts = for_assigned_stmts
nodes.Comprehension.assigned_stmts = for_assigned_stmts
@@ -335,6 +337,8 @@ def assign_assigned_stmts(self, node, context=None, asspath=None):
return
for inferred in _resolve_asspart(self.value.infer(context), asspath, context):
yield inferred
+ raise exceptions.DefaultStop(node=self, unknown=node,
+ assign_path=asspath, context=context)
nodes.Assign.assigned_stmts = assign_assigned_stmts
nodes.AugAssign.assigned_stmts = assign_assigned_stmts
@@ -375,6 +379,9 @@ def excepthandler_assigned_stmts(self, node, context=None, asspath=None):
if isinstance(assigned, nodes.ClassDef):
assigned = bases.Instance(assigned)
yield assigned
+ raise exceptions.DefaultStop(node=self, unknown=node,
+ assign_path=asspath, context=context)
+
nodes.ExceptHandler.assigned_stmts = excepthandler_assigned_stmts
@@ -415,7 +422,7 @@ def _infer_context_manager(self, mgr, context):
elif isinstance(inferred, bases.Instance):
try:
enter = next(inferred.igetattr('__enter__', context=context))
- except (exceptions.InferenceError, exceptions.NotFoundError):
+ except (exceptions.InferenceError, exceptions.AttributeInferenceError):
return
if not isinstance(enter, bases.BoundMethod):
return
@@ -443,8 +450,13 @@ def with_assigned_stmts(self, node, context=None, asspath=None):
pass
# ContextManager().infer() will return ContextManager
# f.infer() will return 42.
- """
+ Arguments:
+ self: nodes.With
+ node: The target of the assignment, `as (a, b)` in `with foo as (a, b)`.
+ context: TODO
+ asspath: TODO
+ """
mgr = next(mgr for (mgr, vars) in self.items if vars == node)
if asspath is None:
for result in _infer_context_manager(self, mgr, context):
@@ -455,30 +467,49 @@ def with_assigned_stmts(self, node, context=None, asspath=None):
obj = result
for index in asspath:
if not hasattr(obj, 'elts'):
- raise exceptions.InferenceError
+ raise exceptions.InferenceError(
+ 'Wrong type ({targets!r}) for {node!r} assignment',
+ node=self, targets=node, assign_path=asspath,
+ context=context)
try:
obj = obj.elts[index]
except IndexError:
- util.reraise(exceptions.InferenceError())
+ util.reraise(exceptions.InferenceError(
+ 'Tried to infer a nonexistent target with index {index} '
+ 'in {node!r}.', node=self, targets=node,
+ assign_path=asspath, context=context))
yield obj
-
+ raise exceptions.DefaultStop(node=self, unknown=node,
+ assign_path=asspath, context=context)
nodes.With.assigned_stmts = with_assigned_stmts
@decorators.yes_if_nothing_inferred
def starred_assigned_stmts(self, node=None, context=None, asspath=None):
+ """
+ Arguments:
+ self: nodes.Starred
+ node: TODO
+ context: TODO
+ asspath: TODO
+ """
stmt = self.statement()
if not isinstance(stmt, (nodes.Assign, nodes.For)):
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError('Statement {stmt!r} enclosing {node!r} '
+ 'must be an Assign or For node.',
+ node=self, stmt=stmt, unknown=node,
+ context=context)
if isinstance(stmt, nodes.Assign):
value = stmt.value
lhs = stmt.targets[0]
if sum(1 for node in lhs.nodes_of_class(nodes.Starred)) > 1:
- # Too many starred arguments in the expression.
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError('Too many starred arguments in the '
+ ' assignment targets {lhs!r}.',
+ node=self, targets=lhs,
+ unknown=node, context=context)
if context is None:
context = contextmod.InferenceContext()
@@ -494,8 +525,11 @@ def starred_assigned_stmts(self, node=None, context=None, asspath=None):
elts = collections.deque(rhs.elts[:])
if len(lhs.elts) > len(rhs.elts):
- # a, *b, c = (1, 2)
- raise exceptions.InferenceError()
+ raise exceptions.InferenceError('More targets, {targets!r}, than '
+ 'values to unpack, {values!r}.',
+ node=self, targets=lhs,
+ values=rhs, unknown=node,
+ context=context)
# Unpack iteratively the values from the rhs of the assignment,
# until the find the starred node. What will remain will
diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py
index e6e3324..eb7baf7 100644
--- a/astroid/scoped_nodes.py
+++ b/astroid/scoped_nodes.py
@@ -43,7 +43,7 @@ BUILTINS = six.moves.builtins.__name__
ITER_METHODS = ('__iter__', '__getitem__')
-def _c3_merge(sequences):
+def _c3_merge(sequences, cls, context):
"""Merges MROs in *sequences* to a single MRO using the C3 algorithm.
Adapted from http://www.python.org/download/releases/2.3/mro/.
@@ -65,12 +65,10 @@ def _c3_merge(sequences):
if not candidate:
# Show all the remaining bases, which were considered as
# candidates for the next mro sequence.
- bases = ["({})".format(", ".join(base.name
- for base in subsequence))
- for subsequence in sequences]
raise exceptions.InconsistentMroError(
- "Cannot create a consistent method resolution "
- "order for bases %s" % ", ".join(bases))
+ message="Cannot create a consistent method resolution order "
+ "for MROs {mros} of class {cls!r}.",
+ mros=sequences, cls=cls, context=context)
result.append(candidate)
# remove the chosen candidate
@@ -79,11 +77,13 @@ def _c3_merge(sequences):
del seq[0]
-def _verify_duplicates_mro(sequences):
+def _verify_duplicates_mro(sequences, cls, context):
for sequence in sequences:
names = [node.qname() for node in sequence]
if len(names) != len(set(names)):
- raise exceptions.DuplicateBasesError('Duplicates found in the mro.')
+ raise exceptions.DuplicateBasesError(
+ message='Duplicates found in MROs {mros} for {cls!r}.',
+ mros=sequences, cls=cls, context=context)
def remove_nodes(cls):
@@ -91,7 +91,10 @@ def remove_nodes(cls):
def decorator(func, instance, args, kwargs):
nodes = [n for n in func(*args, **kwargs) if not isinstance(n, cls)]
if not nodes:
- raise exceptions.NotFoundError()
+ # TODO: no way to access the name or context when raising
+ # this error.
+ raise exceptions.AttributeInferenceError(
+ 'No nodes left after filtering.', target=instance)
return nodes
return decorator
@@ -116,7 +119,8 @@ def std_special_attributes(self, name, add_locals=True):
return [node_classes.const_factory(self.doc)] + locals.get(name, [])
if name == '__dict__':
return [node_classes.Dict()] + locals.get(name, [])
- raise exceptions.NotFoundError(name)
+ # TODO: missing context
+ raise exceptions.AttributeInferenceError(target=self, attribute=name)
MANAGER = manager.AstroidManager()
@@ -344,7 +348,7 @@ class Module(LocalsDictNodeNG):
if name in self.scope_attrs and name not in self.locals:
try:
return self, self.getattr(name)
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
return self, ()
return self._scope_lookup(node, name, offset)
@@ -368,8 +372,11 @@ class Module(LocalsDictNodeNG):
try:
return [self.import_module(name, relative_only=True)]
except (exceptions.AstroidBuildingException, SyntaxError):
- util.reraise(exceptions.NotFoundError(name))
- raise exceptions.NotFoundError(name)
+ util.reraise(exceptions.AttributeInferenceError(target=self,
+ attribute=name,
+ context=context))
+ raise exceptions.AttributeInferenceError(target=self, attribute=name,
+ context=context)
def igetattr(self, name, context=None):
"""inferred getattr"""
@@ -380,8 +387,9 @@ class Module(LocalsDictNodeNG):
try:
return bases._infer_stmts(self.getattr(name, context),
context, frame=self)
- except exceptions.NotFoundError:
- util.reraise(exceptions.InferenceError(name))
+ except exceptions.AttributeInferenceError as error:
+ util.reraise(exceptions.InferenceError(
+ error.message, target=self, attribute=name, context=context))
def fully_defined(self):
"""return True if this module has been built from a .py file
@@ -853,8 +861,9 @@ class FunctionDef(node_classes.Statement, Lambda):
try:
return bases._infer_stmts(self.getattr(name, context),
context, frame=self)
- except exceptions.NotFoundError:
- util.reraise(exceptions.InferenceError(name))
+ except exceptions.AttributeInferenceError as error:
+ util.reraise(exceptions.InferenceError(
+ error.message, target=self, attribute=name, context=context))
def is_method(self):
"""return true if the function node should be considered as a method"""
@@ -1306,7 +1315,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
"""return the list of assign node associated to name in this class
locals or in its parents
- :raises `NotFoundError`:
+ :raises `AttributeInferenceError`:
if no attribute with this name has been find in this class or
its parent classes
"""
@@ -1315,14 +1324,15 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
except KeyError:
for class_node in self.local_attr_ancestors(name, context):
return class_node.locals[name]
- raise exceptions.NotFoundError(name)
+ raise exceptions.AttributeInferenceError(target=self, attribute=name,
+ context=context)
@remove_nodes(node_classes.DelAttr)
def instance_attr(self, name, context=None):
"""return the astroid nodes associated to name in this class instance
attributes dictionary and in its parents
- :raises `NotFoundError`:
+ :raises `AttributeInferenceError`:
if no attribute with this name has been find in this class or
its parent classes
"""
@@ -1333,7 +1343,8 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
for class_node in self.instance_attr_ancestors(name, context):
values += class_node.instance_attrs[name]
if not values:
- raise exceptions.NotFoundError(name)
+ raise exceptions.AttributeInferenceError(target=self, attribute=name,
+ context=context)
return values
def instanciate_class(self):
@@ -1378,7 +1389,8 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
if class_context:
values += self._metaclass_lookup_attribute(name, context)
if not values:
- raise exceptions.NotFoundError(name)
+ raise exceptions.AttributeInferenceError(target=self, attribute=name,
+ context=context)
return values
def _metaclass_lookup_attribute(self, name, context):
@@ -1397,7 +1409,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
try:
attrs = cls.getattr(name, context=context,
class_context=True)
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
return
for attr in bases._infer_stmts(attrs, context, frame=cls):
@@ -1439,18 +1451,19 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
and isinstance(inferred, bases.Instance)):
try:
inferred._proxied.getattr('__get__', context)
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
yield inferred
else:
yield util.YES
else:
yield function_to_method(inferred, self)
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError as error:
if not name.startswith('__') and self.has_dynamic_getattr(context):
# class handle some dynamic attributes, return a YES object
yield util.YES
else:
- util.reraise(exceptions.InferenceError(name))
+ util.reraise(exceptions.InferenceError(
+ error.message, target=self, attribute=name, context=context))
def has_dynamic_getattr(self, context=None):
"""
@@ -1467,12 +1480,12 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
try:
return _valid_getattr(self.getattr('__getattr__', context)[0])
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
#if self.newstyle: XXX cause an infinite recursion error
try:
getattribute = self.getattr('__getattribute__', context)[0]
return _valid_getattr(getattribute)
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
pass
return False
@@ -1592,7 +1605,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
try:
slots.getattr(meth)
break
- except exceptions.NotFoundError:
+ except exceptions.AttributeInferenceError:
continue
else:
continue
@@ -1722,8 +1735,8 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG,
bases_mro.append(ancestors)
unmerged_mro = ([[self]] + bases_mro + [bases])
- _verify_duplicates_mro(unmerged_mro)
- return _c3_merge(unmerged_mro)
+ _verify_duplicates_mro(unmerged_mro, self, context)
+ return _c3_merge(unmerged_mro, self, context)
def bool_value(self):
return True
diff --git a/astroid/tests/unittest_brain.py b/astroid/tests/unittest_brain.py
index 3520b49..ca47d52 100644
--- a/astroid/tests/unittest_brain.py
+++ b/astroid/tests/unittest_brain.py
@@ -134,7 +134,7 @@ class NamedTupleTest(unittest.TestCase):
instance = next(result.infer())
self.assertEqual(len(instance.getattr('scheme')), 1)
self.assertEqual(len(instance.getattr('port')), 1)
- with self.assertRaises(astroid.NotFoundError):
+ with self.assertRaises(astroid.AttributeInferenceError):
instance.getattr('foo')
self.assertEqual(len(instance.getattr('geturl')), 1)
self.assertEqual(instance.name, 'ParseResult')
diff --git a/astroid/tests/unittest_builder.py b/astroid/tests/unittest_builder.py
index 84bf50c..485963b 100644
--- a/astroid/tests/unittest_builder.py
+++ b/astroid/tests/unittest_builder.py
@@ -451,7 +451,7 @@ class BuilderTest(unittest.TestCase):
self.assertIsInstance(astroid.getattr('CSTE')[0], nodes.AssignName)
self.assertEqual(astroid.getattr('CSTE')[0].fromlineno, 2)
self.assertEqual(astroid.getattr('CSTE')[1].fromlineno, 6)
- with self.assertRaises(exceptions.NotFoundError):
+ with self.assertRaises(exceptions.AttributeInferenceError):
astroid.getattr('CSTE2')
with self.assertRaises(exceptions.InferenceError):
next(astroid['global_no_effect'].ilookup('CSTE2'))
diff --git a/astroid/tests/unittest_lookup.py b/astroid/tests/unittest_lookup.py
index 805efd9..7f9c43a 100644
--- a/astroid/tests/unittest_lookup.py
+++ b/astroid/tests/unittest_lookup.py
@@ -174,7 +174,7 @@ class LookupTest(resources.SysPathSetup, unittest.TestCase):
if sys.version_info < (3, 0):
self.assertEqual(var.inferred(), [util.YES])
else:
- self.assertRaises(exceptions.UnresolvableName, var.inferred)
+ self.assertRaises(exceptions.NameInferenceError, var.inferred)
def test_dict_comps(self):
astroid = builder.parse("""
@@ -210,7 +210,7 @@ class LookupTest(resources.SysPathSetup, unittest.TestCase):
var
""")
var = astroid.body[1].value
- self.assertRaises(exceptions.UnresolvableName, var.inferred)
+ self.assertRaises(exceptions.NameInferenceError, var.inferred)
def test_generator_attributes(self):
tree = builder.parse("""
@@ -250,7 +250,7 @@ class LookupTest(resources.SysPathSetup, unittest.TestCase):
self.assertTrue(p2.getattr('__name__'))
self.assertTrue(astroid['NoName'].getattr('__name__'))
p3 = next(astroid['p3'].infer())
- self.assertRaises(exceptions.NotFoundError, p3.getattr, '__name__')
+ self.assertRaises(exceptions.AttributeInferenceError, p3.getattr, '__name__')
def test_function_module_special(self):
astroid = builder.parse('''
diff --git a/astroid/tests/unittest_nodes.py b/astroid/tests/unittest_nodes.py
index ade8a92..55c7268 100644
--- a/astroid/tests/unittest_nodes.py
+++ b/astroid/tests/unittest_nodes.py
@@ -331,13 +331,13 @@ class ImportNodeTest(resources.SysPathSetup, unittest.TestCase):
self.assertEqual(from_.real_name('NameNode'), 'Name')
imp_ = self.module['os']
self.assertEqual(imp_.real_name('os'), 'os')
- self.assertRaises(exceptions.NotFoundError, imp_.real_name, 'os.path')
+ self.assertRaises(exceptions.AttributeInferenceError, imp_.real_name, 'os.path')
imp_ = self.module['NameNode']
self.assertEqual(imp_.real_name('NameNode'), 'Name')
- self.assertRaises(exceptions.NotFoundError, imp_.real_name, 'Name')
+ self.assertRaises(exceptions.AttributeInferenceError, imp_.real_name, 'Name')
imp_ = self.module2['YO']
self.assertEqual(imp_.real_name('YO'), 'YO')
- self.assertRaises(exceptions.NotFoundError, imp_.real_name, 'data')
+ self.assertRaises(exceptions.AttributeInferenceError, imp_.real_name, 'data')
def test_as_string(self):
ast = self.module['modutils']
@@ -502,7 +502,7 @@ class UnboundMethodNodeTest(unittest.TestCase):
meth = A.test
''')
node = next(ast['meth'].infer())
- with self.assertRaises(exceptions.NotFoundError):
+ with self.assertRaises(exceptions.AttributeInferenceError):
node.getattr('__missssing__')
name = node.getattr('__name__')[0]
self.assertIsInstance(name, nodes.Const)
diff --git a/astroid/tests/unittest_objects.py b/astroid/tests/unittest_objects.py
index e0a04d5..dc91d94 100644
--- a/astroid/tests/unittest_objects.py
+++ b/astroid/tests/unittest_objects.py
@@ -233,15 +233,15 @@ class SuperTests(unittest.TestCase):
self.assertIsInstance(first, objects.Super)
with self.assertRaises(exceptions.SuperError) as cm:
first.super_mro()
- self.assertEqual(str(cm.exception), "The first super argument must be type.")
- for node in ast_nodes[1:]:
+ self.assertIsInstance(cm.exception.super_.mro_pointer, nodes.Const)
+ self.assertEqual(cm.exception.super_.mro_pointer.value, 1)
+ for node, invalid_type in zip(ast_nodes[1:],
+ (nodes.Const, bases.Instance)):
inferred = next(node.infer())
self.assertIsInstance(inferred, objects.Super, node)
- with self.assertRaises(exceptions.SuperArgumentTypeError) as cm:
+ with self.assertRaises(exceptions.SuperError) as cm:
inferred.super_mro()
- self.assertEqual(str(cm.exception),
- "super(type, obj): obj must be an instance "
- "or subtype of type", node)
+ self.assertIsInstance(cm.exception.super_.type, invalid_type)
def test_proxied(self):
node = test_utils.extract_node('''
@@ -338,9 +338,9 @@ class SuperTests(unittest.TestCase):
with self.assertRaises(exceptions.InferenceError):
next(ast_nodes[2].infer())
fourth = next(ast_nodes[3].infer())
- with self.assertRaises(exceptions.NotFoundError):
+ with self.assertRaises(exceptions.AttributeInferenceError):
fourth.getattr('test3')
- with self.assertRaises(exceptions.NotFoundError):
+ with self.assertRaises(exceptions.AttributeInferenceError):
next(fourth.igetattr('test3'))
first_unbound = next(ast_nodes[4].infer())
@@ -362,7 +362,7 @@ class SuperTests(unittest.TestCase):
super(Super, self) #@
''')
inferred = next(node.infer())
- with self.assertRaises(exceptions.NotFoundError):
+ with self.assertRaises(exceptions.AttributeInferenceError):
next(inferred.getattr('test'))
def test_super_complex_mro(self):
@@ -491,7 +491,7 @@ class SuperTests(unittest.TestCase):
inferred = next(node.infer())
with self.assertRaises(exceptions.SuperError):
inferred.super_mro()
- with self.assertRaises(exceptions.SuperArgumentTypeError):
+ with self.assertRaises(exceptions.SuperError):
inferred.super_mro()
diff --git a/astroid/tests/unittest_scoped_nodes.py b/astroid/tests/unittest_scoped_nodes.py
index 329aa69..47807aa 100644
--- a/astroid/tests/unittest_scoped_nodes.py
+++ b/astroid/tests/unittest_scoped_nodes.py
@@ -24,12 +24,13 @@ from functools import partial
import unittest
import warnings
+import astroid
from astroid import builder
from astroid import nodes
from astroid import scoped_nodes
from astroid import util
from astroid.exceptions import (
- InferenceError, NotFoundError,
+ InferenceError, AttributeInferenceError,
NoDefault, ResolveError, MroError,
InconsistentMroError, DuplicateBasesError,
)
@@ -75,7 +76,7 @@ class ModuleNodeTest(ModuleLoader, unittest.TestCase):
os.path.abspath(resources.find('data/module.py')))
self.assertEqual(len(self.module.getattr('__dict__')), 1)
self.assertIsInstance(self.module.getattr('__dict__')[0], nodes.Dict)
- self.assertRaises(NotFoundError, self.module.getattr, '__path__')
+ self.assertRaises(AttributeInferenceError, self.module.getattr, '__path__')
self.assertEqual(len(self.pack.getattr('__path__')), 1)
self.assertIsInstance(self.pack.getattr('__path__')[0], nodes.List)
@@ -101,7 +102,6 @@ class ModuleNodeTest(ModuleLoader, unittest.TestCase):
self.assertEqual(cnx.name, 'Connection')
self.assertEqual(cnx.root().name, 'data.SSL1.Connection1')
self.assertEqual(len(self.nonregr.getattr('enumerate')), 2)
- # raise ResolveError
self.assertRaises(InferenceError, self.nonregr.igetattr, 'YOAA')
def test_wildcard_import_names(self):
@@ -574,7 +574,7 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
self.assertEqual(cls.getattr('__module__')[0].value, 'data.module')
self.assertEqual(len(cls.getattr('__dict__')), 1)
if not cls.newstyle:
- self.assertRaises(NotFoundError, cls.getattr, '__mro__')
+ self.assertRaises(AttributeInferenceError, cls.getattr, '__mro__')
for cls in (nodes.List._proxied, nodes.Const(1)._proxied):
self.assertEqual(len(cls.getattr('__bases__')), 1)
self.assertEqual(len(cls.getattr('__name__')), 1)
@@ -620,9 +620,9 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
def test_instance_special_attributes(self):
for inst in (Instance(self.module['YO']), nodes.List(), nodes.Const(1)):
- self.assertRaises(NotFoundError, inst.getattr, '__mro__')
- self.assertRaises(NotFoundError, inst.getattr, '__bases__')
- self.assertRaises(NotFoundError, inst.getattr, '__name__')
+ self.assertRaises(AttributeInferenceError, inst.getattr, '__mro__')
+ self.assertRaises(AttributeInferenceError, inst.getattr, '__bases__')
+ self.assertRaises(AttributeInferenceError, inst.getattr, '__name__')
self.assertEqual(len(inst.getattr('__dict__')), 1)
self.assertEqual(len(inst.getattr('__doc__')), 1)
@@ -718,7 +718,7 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
method_locals = klass2.local_attr('method')
self.assertEqual(len(method_locals), 1)
self.assertEqual(method_locals[0].name, 'method')
- self.assertRaises(NotFoundError, klass2.local_attr, 'nonexistant')
+ self.assertRaises(AttributeInferenceError, klass2.local_attr, 'nonexistant')
methods = {m.name for m in klass2.methods()}
self.assertTrue(methods.issuperset(expected_methods))
@@ -1297,18 +1297,16 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
self.assertEqualMro(astroid['E1'], ['E1', 'C1', 'B1', 'A1', 'object'])
with self.assertRaises(InconsistentMroError) as cm:
astroid['F1'].mro()
- self.assertEqual(str(cm.exception),
- "Cannot create a consistent method resolution order "
- "for bases (B1, C1, A1, object), "
- "(C1, B1, A1, object)")
-
+ A1 = astroid.getattr('A1')[0]
+ B1 = astroid.getattr('B1')[0]
+ C1 = astroid.getattr('C1')[0]
+ object_ = builder.MANAGER.astroid_cache[BUILTINS].getattr('object')[0]
+ self.assertEqual(cm.exception.mros, [[B1, C1, A1, object_],
+ [C1, B1, A1, object_]])
with self.assertRaises(InconsistentMroError) as cm:
astroid['G1'].mro()
- self.assertEqual(str(cm.exception),
- "Cannot create a consistent method resolution order "
- "for bases (C1, B1, A1, object), "
- "(B1, C1, A1, object)")
-
+ self.assertEqual(cm.exception.mros, [[C1, B1, A1, object_],
+ [B1, C1, A1, object_]])
self.assertEqualMro(
astroid['PedalWheelBoat'],
["PedalWheelBoat", "EngineLess",
@@ -1329,9 +1327,10 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
with self.assertRaises(DuplicateBasesError) as cm:
astroid['Duplicates'].mro()
- self.assertEqual(str(cm.exception), "Duplicates found in the mro.")
- self.assertTrue(issubclass(cm.exception.__class__, MroError))
- self.assertTrue(issubclass(cm.exception.__class__, ResolveError))
+ Duplicates = astroid.getattr('Duplicates')[0]
+ self.assertEqual(cm.exception.cls, Duplicates)
+ self.assertIsInstance(cm.exception, MroError)
+ self.assertIsInstance(cm.exception, ResolveError)
def test_generator_from_infer_call_result_parent(self):
func = test_utils.extract_node("""
@@ -1357,7 +1356,7 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
self.assertEqual(first["a"].value, 1)
self.assertIsInstance(first["b"], nodes.Const)
self.assertEqual(first["b"].value, 2)
- with self.assertRaises(NotFoundError):
+ with self.assertRaises(AttributeInferenceError):
first.getattr("missing")
def test_implicit_metaclass(self):
@@ -1376,7 +1375,7 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
instance = cls.instanciate_class()
func = cls.getattr('mro')
self.assertEqual(len(func), 1)
- self.assertRaises(NotFoundError, instance.getattr, 'mro')
+ self.assertRaises(AttributeInferenceError, instance.getattr, 'mro')
def test_metaclass_lookup_using_same_class(self):
# Check that we don't have recursive attribute access for metaclass
diff --git a/astroid/util.py b/astroid/util.py
index 20c44d7..90ea7d6 100644
--- a/astroid/util.py
+++ b/astroid/util.py
@@ -49,6 +49,41 @@ class YES(object):
return self
+class BadOperationMessage(object):
+ """Object which describes a TypeError occurred somewhere in the inference chain
+
+ This is not an exception, but a container object which holds the types and
+ the error which occurred.
+ """
+
+
+class BadUnaryOperationMessage(BadOperationMessage):
+ """Object which describes operational failures on UnaryOps."""
+
+ def __init__(self, operand, op, error):
+ self.operand = operand
+ self.op = op
+ self.error = error
+
+ def __str__(self):
+ operand_type = self.operand.name
+ msg = "bad operand type for unary {}: {}"
+ return msg.format(self.op, operand_type)
+
+
+class BadBinaryOperationMessage(BadOperationMessage):
+ """Object which describes type errors for BinOps."""
+
+ def __init__(self, left_type, op, right_type):
+ self.left_type = left_type
+ self.right_type = right_type
+ self.op = op
+
+ def __str__(self):
+ msg = "unsupported operand type(s) for {}: {!r} and {!r}"
+ return msg.format(self.op, self.left_type.name, self.right_type.name)
+
+
def _instancecheck(cls, other):
wrapped = cls.__wrapped__
other_cls = other.__class__
diff --git a/tox.ini b/tox.ini
index 53c797e..8b23ed2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,6 +2,7 @@
# official list is
# envlist = py27, py33, py34, pypy, jython
envlist = py27, py33, pylint
+# envlist = py27, py34
[testenv:pylint]
deps =
@@ -29,4 +30,4 @@ deps =
six
wrapt
-commands = python -m unittest discover -s {envsitepackagesdir}/astroid/tests -p "unittest*.py"
+commands = python -m unittest discover -s {envsitepackagesdir}/astroid/tests -p "unittest*.py"