From 1ba0f2d96fbc45ff0b6014b12db98716183e8277 Mon Sep 17 00:00:00 2001 From: Ceridwen Date: Mon, 2 Nov 2015 00:10:54 -0500 Subject: 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. --- astroid/arguments.py | 39 +++++-- astroid/bases.py | 31 +++--- astroid/brain/brain_builtin_inference.py | 10 +- astroid/brain/brain_gi.py | 4 +- astroid/brain/brain_six.py | 2 +- astroid/builder.py | 23 ++-- astroid/decorators.py | 16 ++- astroid/exceptions.py | 178 +++++++++++++++++++++++-------- astroid/helpers.py | 2 +- astroid/inference.py | 65 ++++++----- astroid/manager.py | 49 +++++---- astroid/mixins.py | 20 +++- astroid/node_classes.py | 17 +-- astroid/objects.py | 43 +++++--- astroid/protocols.py | 56 ++++++++-- astroid/scoped_nodes.py | 75 +++++++------ astroid/tests/unittest_brain.py | 2 +- astroid/tests/unittest_builder.py | 2 +- astroid/tests/unittest_lookup.py | 6 +- astroid/tests/unittest_nodes.py | 8 +- astroid/tests/unittest_objects.py | 20 ++-- astroid/tests/unittest_scoped_nodes.py | 45 ++++---- astroid/util.py | 35 ++++++ tox.ini | 3 +- 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 '' % (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 . """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" -- cgit v1.2.1 From bc55c015163aab7d6be292f67687afec07aeacae Mon Sep 17 00:00:00 2001 From: Ceridwen Date: Tue, 3 Nov 2015 00:42:57 -0500 Subject: Improve InferenceError for remove_nodes --- astroid/decorators.py | 1 + astroid/scoped_nodes.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/astroid/decorators.py b/astroid/decorators.py index 27ce983..815b540 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -129,5 +129,6 @@ def raise_if_nothing_inferred(func, instance, args, kwargs): yield node except exceptions.DefaultStop as e: fields = vars(e) + del fields['message'] if not inferred: raise exceptions.InferenceError(**fields) diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index eb7baf7..9b52135 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -91,10 +91,11 @@ 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: - # TODO: no way to access the name or context when raising - # this error. + # TODO: no way to access the context when raising this error. raise exceptions.AttributeInferenceError( - 'No nodes left after filtering.', target=instance) + 'No nodes left after removing all {remove_type!r} from ' + 'inferring for {node!r}.', + node=instance, remove_type=cls) return nodes return decorator -- cgit v1.2.1 From 2a0ae68ad53ca60b7a31124ea2bef5c219d4e03b Mon Sep 17 00:00:00 2001 From: Ceridwen Date: Tue, 3 Nov 2015 15:01:38 -0500 Subject: Improve InferenceError for remove_nodes --- astroid/scoped_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index 9b52135..3b1e18a 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -94,7 +94,7 @@ def remove_nodes(cls): # TODO: no way to access the context when raising this error. raise exceptions.AttributeInferenceError( 'No nodes left after removing all {remove_type!r} from ' - 'inferring for {node!r}.', + 'nodes inferred for {node!r}.', node=instance, remove_type=cls) return nodes return decorator -- cgit v1.2.1 From 336760405946c1ee0c8f1dc1e194b69ae73ed857 Mon Sep 17 00:00:00 2001 From: Ceridwen Date: Fri, 6 Nov 2015 10:48:59 -0500 Subject: Add structured exceptions to decorators and remaining functions. * Use explicit StopIteration to pass information from generators to raise_if_nothing_inferred and path_wrapper, rather than return or implicit termination by reaching the end of the code block. * Remove remove_nodes in favor of handling the cases in local_attr, istance_attr, and getattr, to avoid the need for complicated information passing when needing to raise an exception. --- astroid/decorators.py | 63 +++++++++++++++++++++++++++++++++--------------- astroid/exceptions.py | 12 ---------- astroid/inference.py | 28 ++++++++++++++++------ astroid/protocols.py | 27 +++++++++++++-------- astroid/scoped_nodes.py | 64 +++++++++++++++++++++++-------------------------- 5 files changed, 112 insertions(+), 82 deletions(-) diff --git a/astroid/decorators.py b/astroid/decorators.py index 815b540..e60eb49 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -93,15 +93,26 @@ def path_wrapper(func): return yielded = set() - for res in _func(node, context, **kwargs): - # unproxy only true instance, not const, tuple, dict... - if res.__class__.__name__ == 'Instance': - ares = res._proxied + generator = _func(node, context, **kwargs) + try: + while True: + res = next(generator) + # unproxy only true instance, not const, tuple, dict... + if res.__class__.__name__ == 'Instance': + ares = res._proxied + else: + ares = res + if ares not in yielded: + yield res + yielded.add(ares) + except StopIteration as error: + # Explicit StopIteration to return error information, see + # comment in raise_if_nothing_inferred. + if len(error.args) > 0: + util.reraise(StopIteration(error.args[0])) else: - ares = res - if ares not in yielded: - yield res - yielded.add(ares) + util.reraise(StopIteration()) + return wrapped @@ -117,18 +128,32 @@ 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. + '''All generators wrapped with raise_if_nothing_inferred *must* + explicitly raise StopIteration with information to create an + appropriate structured InferenceError. + ''' + # TODO: Explicitly raising StopIteration in a generator will cause + # a RuntimeError in Python >=3.7, as per + # http://legacy.python.org/dev/peps/pep-0479/ . Before 3.7 is + # released, this code will need to use one of four possible + # solutions: a decorator that restores the current behavior as + # described in + # http://legacy.python.org/dev/peps/pep-0479/#sub-proposal-decorator-to-explicitly-request-current-behaviour + # , dynamic imports or exec to generate different code for + # different versions, drop support for all Python versions <3.3, + # or refactoring to change how these decorators work. In any + # event, after dropping support for Python <3.3 this code should + # be refactored to use `yield from`. inferred = False - fields = {} try: - for node in func(*args, **kwargs): + generator = func(*args, **kwargs) + while True: + yield next(generator) inferred = True - yield node - except exceptions.DefaultStop as e: - fields = vars(e) - del fields['message'] - if not inferred: - raise exceptions.InferenceError(**fields) + except StopIteration as error: + if not inferred: + if len(error.args) > 0: + util.reraise(exceptions.InferenceError(**error.args[0])) + else: + util.reraise(exceptions.InferenceError()) diff --git a/astroid/exceptions.py b/astroid/exceptions.py index 9f49753..ede21d9 100644 --- a/astroid/exceptions.py +++ b/astroid/exceptions.py @@ -69,18 +69,6 @@ class NoDefault(AstroidError): 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. diff --git a/astroid/inference.py b/astroid/inference.py index b5e45df..fda4546 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -118,7 +118,9 @@ def infer_call(self, context=None): except exceptions.InferenceError: ## XXX log error ? continue - raise exceptions.DefaultStop(node=self, context=context) + # Explicit StopIteration to return error information, see comment + # in raise_if_nothing_inferred. + raise StopIteration(dict(node=self, context=context)) nodes.Call._infer = infer_call @@ -179,7 +181,9 @@ def infer_attribute(self, context=None): except AttributeError: # XXX method / function context.boundnode = None - raise exceptions.DefaultStop(node=self, context=context) + # Explicit StopIteration to return error information, see comment + # in raise_if_nothing_inferred. + raise StopIteration(dict(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 @@ -282,7 +286,9 @@ def infer_subscript(self, context=None): for inferred in assigned.infer(context): yield inferred - raise exceptions.DefaultStop(node=self, context=context) + # Explicit StopIteration to return error information, see comment + # in raise_if_nothing_inferred. + raise StopIteration(dict(node=self, context=context)) nodes.Subscript._infer = decorators.path_wrapper(infer_subscript) nodes.Subscript.infer_lhs = infer_subscript @@ -338,7 +344,9 @@ def _infer_boolop(self, context=None): else: yield value - raise exceptions.DefaultStop(node=self, context=context) + # Explicit StopIteration to return error information, see comment + # in raise_if_nothing_inferred. + raise StopIteration(dict(node=self, context=context)) nodes.BoolOp._infer = _infer_boolop @@ -411,7 +419,9 @@ def infer_unaryop(self, context=None): for inferred in _filter_operation_errors(self, _infer_unaryop, context, util.BadUnaryOperationMessage): yield inferred - raise exceptions.DefaultStop(node=self, context=context) + # Explicit StopIteration to return error information, see comment + # in raise_if_nothing_inferred. + raise StopIteration(dict(node=self, context=context)) nodes.UnaryOp._infer_unaryop = _infer_unaryop nodes.UnaryOp._infer = infer_unaryop @@ -728,11 +738,15 @@ def instance_getitem(self, index, context=None): method = next(self.igetattr('__getitem__', context=context)) if not isinstance(method, bases.BoundMethod): - raise exceptions.InferenceError(node=self, context=context) + raise exceptions.InferenceError( + 'Could not find __getitem__ for {node!r}.', + node=self, context=context) try: return next(method.infer_call_result(self, new_context)) except StopIteration: - util.reraise(exceptions.InferenceError(node=self, context=context)) + util.reraise(exceptions.InferenceError( + message='Inference for {node!r}[{index!s}] failed.', + node=self, index=index.value, context=context)) bases.Instance.getitem = instance_getitem diff --git a/astroid/protocols.py b/astroid/protocols.py index 2e01124..5809e4a 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -249,9 +249,10 @@ 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) - + # Explicit StopIteration to return error information, see comment + # in raise_if_nothing_inferred. + raise StopIteration(dict(node=self, unknown=node, + assign_path=asspath, context=context)) nodes.For.assigned_stmts = for_assigned_stmts nodes.Comprehension.assigned_stmts = for_assigned_stmts @@ -337,8 +338,10 @@ 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) + # Explicit StopIteration to return error information, see comment + # in raise_if_nothing_inferred. + raise StopIteration(dict(node=self, unknown=node, + assign_path=asspath, context=context)) nodes.Assign.assigned_stmts = assign_assigned_stmts nodes.AugAssign.assigned_stmts = assign_assigned_stmts @@ -379,9 +382,11 @@ 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) - + # Explicit StopIteration to return error information, see comment + # in raise_if_nothing_inferred. + raise StopIteration(dict(node=self, unknown=node, + assign_path=asspath, context=context)) + nodes.ExceptHandler.assigned_stmts = excepthandler_assigned_stmts @@ -479,8 +484,10 @@ def with_assigned_stmts(self, node, context=None, asspath=None): '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) + # Explicit StopIteration to return error information, see comment + # in raise_if_nothing_inferred. + raise StopIteration(dict(node=self, unknown=node, + assign_path=asspath, context=context)) nodes.With.assigned_stmts = with_assigned_stmts diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index 3b1e18a..4f5e39d 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -86,20 +86,6 @@ def _verify_duplicates_mro(sequences, cls, context): mros=sequences, cls=cls, context=context) -def remove_nodes(cls): - @wrapt.decorator - def decorator(func, instance, args, kwargs): - nodes = [n for n in func(*args, **kwargs) if not isinstance(n, cls)] - if not nodes: - # TODO: no way to access the context when raising this error. - raise exceptions.AttributeInferenceError( - 'No nodes left after removing all {remove_type!r} from ' - 'nodes inferred for {node!r}.', - node=instance, remove_type=cls) - return nodes - return decorator - - def function_to_method(n, klass): if isinstance(n, FunctionDef): if n.type == 'classmethod': @@ -359,23 +345,28 @@ class Module(LocalsDictNodeNG): def display_type(self): return 'Module' - @remove_nodes(node_classes.DelName) def getattr(self, name, context=None, ignore_locals=False): + result = [] if name in self.special_attributes: if name == '__file__': - return [node_classes.const_factory(self.file)] + self.locals.get(name, []) - if name == '__path__' and self.package: - return [node_classes.List()] + self.locals.get(name, []) - return std_special_attributes(self, name) - if not ignore_locals and name in self.locals: - return self.locals[name] - if self.package: + result = ([node_classes.const_factory(self.file)] + + self.locals.get(name, [])) + elif name == '__path__' and self.package: + result = [node_classes.List()] + self.locals.get(name, []) + else: + result = std_special_attributes(self, name) + elif not ignore_locals and name in self.locals: + result = self.locals[name] + elif self.package: try: - return [self.import_module(name, relative_only=True)] + result = [self.import_module(name, relative_only=True)] except (exceptions.AstroidBuildingException, SyntaxError): util.reraise(exceptions.AttributeInferenceError(target=self, attribute=name, context=context)) + result = [n for n in result if not isinstance(n, node_classes.DelName)] + if result: + return result raise exceptions.AttributeInferenceError(target=self, attribute=name, context=context) @@ -1311,7 +1302,6 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, def has_base(self, node): return node in self.bases - @remove_nodes(node_classes.DelAttr) def local_attr(self, name, context=None): """return the list of assign node associated to name in this class locals or in its parents @@ -1320,15 +1310,20 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, if no attribute with this name has been find in this class or its parent classes """ - try: - return self.locals[name] - except KeyError: - for class_node in self.local_attr_ancestors(name, context): - return class_node.locals[name] + result = [] + if name in self.locals: + result = self.locals[name] + else: + try: + result = next(self.local_attr_ancestors(name, context)).locals[name] + except StopIteration: + pass + result = [n for n in result if not isinstance(n, node_classes.DelAttr)] + if result: + return result 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 @@ -1343,10 +1338,11 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, # get all values from parents for class_node in self.instance_attr_ancestors(name, context): values += class_node.instance_attrs[name] - if not values: - raise exceptions.AttributeInferenceError(target=self, attribute=name, - context=context) - return values + values = [n for n in values if not isinstance(n, node_classes.DelAttr)] + if values: + return values + raise exceptions.AttributeInferenceError(target=self, attribute=name, + context=context) def instanciate_class(self): """return Instance of ClassDef node, else return self""" -- cgit v1.2.1 From 9cfa2d038e875e7f112bd9e4fd960d2571c13131 Mon Sep 17 00:00:00 2001 From: Ceridwen Date: Fri, 6 Nov 2015 12:07:52 -0500 Subject: Use raise directly rather than reraise() in decorators: reraise in this case only adds noise to the tracebacks on 2.7, and when this code is changed to use yield from, there shouldn't be additional tracebacks on 3 either --- astroid/decorators.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/astroid/decorators.py b/astroid/decorators.py index e60eb49..2f02962 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -109,9 +109,9 @@ def path_wrapper(func): # Explicit StopIteration to return error information, see # comment in raise_if_nothing_inferred. if len(error.args) > 0: - util.reraise(StopIteration(error.args[0])) + raise StopIteration(error.args[0]) else: - util.reraise(StopIteration()) + raise StopIteration return wrapped @@ -154,6 +154,7 @@ def raise_if_nothing_inferred(func, instance, args, kwargs): except StopIteration as error: if not inferred: if len(error.args) > 0: - util.reraise(exceptions.InferenceError(**error.args[0])) + raise exceptions.InferenceError(**error.args[0]) else: - util.reraise(exceptions.InferenceError()) + raise exceptions.InferenceError( + 'StopIteration raised without any error information.') -- cgit v1.2.1 From 67dc5fda9c487bbbe6b3ac1e98af76b63d13396e Mon Sep 17 00:00:00 2001 From: Ceridwen Date: Fri, 6 Nov 2015 16:10:08 -0500 Subject: Use next()'s default argument --- astroid/scoped_nodes.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index 4f5e39d..a994392 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -1314,10 +1314,9 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, if name in self.locals: result = self.locals[name] else: - try: - result = next(self.local_attr_ancestors(name, context)).locals[name] - except StopIteration: - pass + class_node = next(self.local_attr_ancestors(name, context), ()) + if class_node: + result = class_node.locals[name] result = [n for n in result if not isinstance(n, node_classes.DelAttr)] if result: return result -- cgit v1.2.1 From cc7ea53df2383cd188e2b0958b231e1eb70fc79c Mon Sep 17 00:00:00 2001 From: Ceridwen Date: Fri, 6 Nov 2015 18:06:45 -0500 Subject: Rename Uninferable and instatiate_class --- astroid/__init__.py | 2 +- astroid/arguments.py | 24 +++---- astroid/bases.py | 16 ++--- astroid/brain/brain_builtin_inference.py | 46 ++++++------- astroid/brain/brain_stdlib.py | 4 +- astroid/builder.py | 2 +- astroid/decorators.py | 2 +- astroid/helpers.py | 8 +-- astroid/inference.py | 76 +++++++++++----------- astroid/manager.py | 2 +- astroid/node_classes.py | 14 ++-- astroid/protocols.py | 32 ++++----- astroid/scoped_nodes.py | 38 +++++------ astroid/tests/unittest_brain.py | 2 +- astroid/tests/unittest_builder.py | 2 +- astroid/tests/unittest_helpers.py | 8 +-- astroid/tests/unittest_inference.py | 108 +++++++++++++++---------------- astroid/tests/unittest_lookup.py | 2 +- astroid/tests/unittest_nodes.py | 4 +- astroid/tests/unittest_protocols.py | 10 +-- astroid/tests/unittest_regrtest.py | 2 +- astroid/tests/unittest_scoped_nodes.py | 6 +- astroid/util.py | 4 +- 23 files changed, 207 insertions(+), 207 deletions(-) diff --git a/astroid/__init__.py b/astroid/__init__.py index f67bbf9..973bb84 100644 --- a/astroid/__init__.py +++ b/astroid/__init__.py @@ -63,7 +63,7 @@ from astroid.bases import Instance, BoundMethod, UnboundMethod from astroid.node_classes import are_exclusive, unpack_infer from astroid.scoped_nodes import builtin_lookup from astroid.builder import parse -from astroid.util import YES +from astroid.util import Uninferable # make a manager instance (borg) accessible from astroid package from astroid.manager import AstroidManager diff --git a/astroid/arguments.py b/astroid/arguments.py index 6483189..7136834 100644 --- a/astroid/arguments.py +++ b/astroid/arguments.py @@ -44,11 +44,11 @@ class CallSite(object): self.positional_arguments = [ arg for arg in self._unpacked_args - if arg is not util.YES + if arg is not util.Uninferable ] self.keyword_arguments = { key: value for key, value in self._unpacked_kwargs.items() - if value is not util.YES + if value is not util.Uninferable } @classmethod @@ -87,29 +87,29 @@ class CallSite(object): try: inferred = next(value.infer(context=context)) except exceptions.InferenceError: - values[name] = util.YES + values[name] = util.Uninferable continue if not isinstance(inferred, nodes.Dict): # Not something we can work with. - values[name] = util.YES + values[name] = util.Uninferable continue for dict_key, dict_value in inferred.items: try: dict_key = next(dict_key.infer(context=context)) except exceptions.InferenceError: - values[name] = util.YES + values[name] = util.Uninferable continue if not isinstance(dict_key, nodes.Const): - values[name] = util.YES + values[name] = util.Uninferable continue if not isinstance(dict_key.value, six.string_types): - values[name] = util.YES + values[name] = util.Uninferable continue if dict_key.value in values: # The name is already in the dictionary - values[dict_key.value] = util.YES + values[dict_key.value] = util.Uninferable self.duplicated_keywords.add(dict_key.value) continue values[dict_key.value] = dict_value @@ -126,14 +126,14 @@ class CallSite(object): try: inferred = next(arg.value.infer(context=context)) except exceptions.InferenceError: - values.append(util.YES) + values.append(util.Uninferable) continue - if inferred is util.YES: - values.append(util.YES) + if inferred is util.Uninferable: + values.append(util.Uninferable) continue if not hasattr(inferred, 'elts'): - values.append(util.YES) + values.append(util.Uninferable) continue values.extend(inferred.elts) else: diff --git a/astroid/bases.py b/astroid/bases.py index 15cf579..05633b8 100644 --- a/astroid/bases.py +++ b/astroid/bases.py @@ -53,7 +53,7 @@ def _is_property(meth): if PROPERTIES.intersection(meth.decoratornames()): return True stripped = {name.split(".")[-1] for name in meth.decoratornames() - if name is not util.YES} + if name is not util.Uninferable} return any(name in stripped for name in POSSIBLE_PROPERTIES) @@ -89,7 +89,7 @@ def _infer_stmts(stmts, context, frame=None): context = contextmod.InferenceContext() for stmt in stmts: - if stmt is util.YES: + if stmt is util.Uninferable: yield stmt inferred = True continue @@ -101,7 +101,7 @@ def _infer_stmts(stmts, context, frame=None): except exceptions.NameInferenceError: continue except exceptions.InferenceError: - yield util.YES + yield util.Uninferable inferred = True if not inferred: raise exceptions.InferenceError( @@ -115,14 +115,14 @@ def _infer_method_result_truth(instance, method_name, context): meth = next(instance.igetattr(method_name, context=context), None) if meth and hasattr(meth, 'infer_call_result'): if not meth.callable(): - return util.YES + return util.Uninferable for value in meth.infer_call_result(instance, context=context): - if value is util.YES: + if value is util.Uninferable: return value inferred = next(value.infer(context=context)) return inferred.bool_value() - return util.YES + return util.Uninferable class Instance(Proxy): @@ -205,7 +205,7 @@ class Instance(Proxy): """infer what a class instance is returning when called""" inferred = False for node in self._proxied.igetattr('__call__', context): - if node is util.YES or not node.callable(): + if node is util.Uninferable or not node.callable(): continue for res in node.infer_call_result(caller, context): inferred = True @@ -296,7 +296,7 @@ class UnboundMethod(Proxy): if (self._proxied.name == '__new__' and self._proxied.parent.frame().qname() == '%s.object' % BUILTINS): infer = caller.args[0].infer() if caller.args else [] - return ((x is util.YES and x or Instance(x)) for x in infer) + return ((x is util.Uninferable and x or Instance(x)) for x in infer) return self._proxied.infer_call_result(caller, context) def bool_value(self): diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py index eb61b70..47ef7f3 100644 --- a/astroid/brain/brain_builtin_inference.py +++ b/astroid/brain/brain_builtin_inference.py @@ -120,10 +120,10 @@ def _generic_inference(node, context, node_type, transform): inferred = next(arg.infer(context=context)) except (InferenceError, StopIteration): raise UseInferenceDefault() - if inferred is util.YES: + if inferred is util.Uninferable: raise UseInferenceDefault() transformed = transform(inferred) - if not transformed or transformed is util.YES: + if not transformed or transformed is util.Uninferable: raise UseInferenceDefault() return transformed @@ -293,7 +293,7 @@ def infer_super(node, context=None): if scope.type == 'classmethod': mro_type = cls else: - mro_type = cls.instanciate_class() + mro_type = cls.instantiate_class() else: # TODO(cpopa): support flow control (multiple inference values). try: @@ -305,7 +305,7 @@ def infer_super(node, context=None): except InferenceError: raise UseInferenceDefault - if mro_pointer is util.YES or mro_type is util.YES: + if mro_pointer is util.Uninferable or mro_type is util.Uninferable: # No way we could understand this. raise UseInferenceDefault @@ -329,11 +329,11 @@ def _infer_getattr_args(node, context): except InferenceError: raise UseInferenceDefault - if obj is util.YES or attr is util.YES: + if obj is util.Uninferable or attr is util.Uninferable: # If one of the arguments is something we can't infer, # then also make the result of the getattr call something # which is unknown. - return util.YES, util.YES + return util.Uninferable, util.Uninferable is_string = (isinstance(attr, nodes.Const) and isinstance(attr.value, six.string_types)) @@ -346,13 +346,13 @@ def _infer_getattr_args(node, context): def infer_getattr(node, context=None): """Understand getattr calls - If one of the arguments is an YES object, then the - result will be an YES object. Otherwise, the normal attribute + If one of the arguments is an Uninferable object, then the + result will be an Uninferable object. Otherwise, the normal attribute lookup will be done. """ obj, attr = _infer_getattr_args(node, context) - if obj is util.YES or attr is util.YES or not hasattr(obj, 'igetattr'): - return util.YES + if obj is util.Uninferable or attr is util.Uninferable or not hasattr(obj, 'igetattr'): + return util.Uninferable try: return next(obj.igetattr(attr, context=context)) @@ -373,17 +373,17 @@ def infer_hasattr(node, context=None): This always guarantees three possible outcomes for calling hasattr: Const(False) when we are sure that the object doesn't have the intended attribute, Const(True) when - we know that the object has the attribute and YES + we know that the object has the attribute and Uninferable when we are unsure of the outcome of the function call. """ try: obj, attr = _infer_getattr_args(node, context) - if obj is util.YES or attr is util.YES or not hasattr(obj, 'getattr'): - return util.YES + if obj is util.Uninferable or attr is util.Uninferable or not hasattr(obj, 'getattr'): + return util.Uninferable obj.getattr(attr, context=context) except UseInferenceDefault: # Can't infer something from this function call. - return util.YES + return util.Uninferable except AttributeInferenceError: # Doesn't have it. return nodes.Const(False) @@ -406,9 +406,9 @@ def infer_callable(node, context=None): try: inferred = next(argument.infer(context=context)) except InferenceError: - return util.YES - if inferred is util.YES: - return util.YES + return util.Uninferable + if inferred is util.Uninferable: + return util.Uninferable return nodes.Const(inferred.callable()) @@ -425,13 +425,13 @@ def infer_bool(node, context=None): try: inferred = next(argument.infer(context=context)) except InferenceError: - return util.YES - if inferred is util.YES: - return util.YES + return util.Uninferable + if inferred is util.Uninferable: + return util.Uninferable bool_value = inferred.bool_value() - if bool_value is util.YES: - return util.YES + if bool_value is util.Uninferable: + return util.Uninferable return nodes.Const(bool_value) @@ -451,7 +451,7 @@ def infer_slice(node, context=None): args = list(map(helpers.safe_infer, args)) for arg in args: - if not arg or arg is util.YES: + if not arg or arg is util.Uninferable: raise UseInferenceDefault if not isinstance(arg, nodes.Const): raise UseInferenceDefault diff --git a/astroid/brain/brain_stdlib.py b/astroid/brain/brain_stdlib.py index a03358f..6a988d6 100644 --- a/astroid/brain/brain_stdlib.py +++ b/astroid/brain/brain_stdlib.py @@ -23,7 +23,7 @@ def infer_func_form(node, base_type, context=None, enum=False): def infer_first(node): try: value = next(node.infer(context=context)) - if value is util.YES: + if value is util.Uninferable: raise UseInferenceDefault() else: return value @@ -330,7 +330,7 @@ def infer_enum_class(node): fake.parent = target.parent for method in node.mymethods(): fake.locals[method.name] = [method] - new_targets.append(fake.instanciate_class()) + new_targets.append(fake.instantiate_class()) node.locals[local] = new_targets break return node diff --git a/astroid/builder.py b/astroid/builder.py index 4db4051..9e06ef7 100644 --- a/astroid/builder.py +++ b/astroid/builder.py @@ -227,7 +227,7 @@ class AstroidBuilder(raw_building.InspectBuilder): try: frame = node.frame() for inferred in node.expr.infer(): - if inferred is util.YES: + if inferred is util.Uninferable: continue try: if inferred.__class__ is bases.Instance: diff --git a/astroid/decorators.py b/astroid/decorators.py index 2f02962..3e9f409 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -123,7 +123,7 @@ def yes_if_nothing_inferred(func, instance, args, kwargs): inferred = True yield node if not inferred: - yield util.YES + yield util.Uninferable @wrapt.decorator diff --git a/astroid/helpers.py b/astroid/helpers.py index d4cd0dd..6d48b0d 100644 --- a/astroid/helpers.py +++ b/astroid/helpers.py @@ -94,9 +94,9 @@ def object_type(node, context=None): try: types = set(_object_type(node, context)) except exceptions.InferenceError: - return util.YES + return util.Uninferable if len(types) > 1 or not types: - return util.YES + return util.Uninferable return list(types)[0] @@ -140,7 +140,7 @@ def has_known_bases(klass, context=None): def _type_check(type1, type2): if not all(map(has_known_bases, (type1, type2))): - return util.YES + return util.Uninferable if not all([type1.newstyle, type2.newstyle]): return False @@ -148,7 +148,7 @@ def _type_check(type1, type2): return type1 in type2.mro()[:-1] except exceptions.MroError: # The MRO is invalid. - return util.YES + return util.Uninferable def is_subtype(type1, type2): diff --git a/astroid/inference.py b/astroid/inference.py index fda4546..47f60d9 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -108,7 +108,7 @@ def infer_call(self, context=None): keywords=self.keywords) callcontext.boundnode = None for callee in self.func.infer(context): - if callee is util.YES: + if callee is util.Uninferable: yield callee continue try: @@ -168,7 +168,7 @@ nodes.ImportFrom._infer = infer_import_from def infer_attribute(self, context=None): """infer an Attribute node by using getattr on the associated object""" for owner in self.expr.infer(context): - if owner is util.YES: + if owner is util.Uninferable: yield owner continue try: @@ -240,13 +240,13 @@ def infer_subscript(self, context=None): """ value = next(self.value.infer(context)) - if value is util.YES: - yield util.YES + if value is util.Uninferable: + yield util.Uninferable return index = next(self.slice.infer(context)) - if index is util.YES: - yield util.YES + if index is util.Uninferable: + yield util.Uninferable return if value.__class__ == bases.Instance: @@ -280,8 +280,8 @@ def infer_subscript(self, context=None): # Prevent inferring if the inferred subscript # is the same as the original subscripted object. - if self is assigned or assigned is util.YES: - yield util.YES + if self is assigned or assigned is util.Uninferable: + yield util.Uninferable return for inferred in assigned.infer(context): yield inferred @@ -312,31 +312,31 @@ def _infer_boolop(self, context=None): try: values = [value.infer(context=context) for value in values] except exceptions.InferenceError: - yield util.YES + yield util.Uninferable return for pair in itertools.product(*values): - if any(item is util.YES for item in pair): - # Can't infer the final result, just yield YES. - yield util.YES + if any(item is util.Uninferable for item in pair): + # Can't infer the final result, just yield Uninferable. + yield util.Uninferable continue bool_values = [item.bool_value() for item in pair] - if any(item is util.YES for item in bool_values): - # Can't infer the final result, just yield YES. - yield util.YES + if any(item is util.Uninferable for item in bool_values): + # Can't infer the final result, just yield Uninferable. + yield util.Uninferable continue # Since the boolean operations are short circuited operations, # this code yields the first value for which the predicate is True # and if no value respected the predicate, then the last value will - # be returned (or YES if there was no last value). + # be returned (or Uninferable if there was no last value). # This is conforming to the semantics of `and` and `or`: # 1 and 0 -> 1 # 0 and 1 -> 0 # 1 or 0 -> 1 # 0 or 1 -> 1 - value = util.YES + value = util.Uninferable for value, bool_value in zip(pair, bool_values): if predicate(bool_value): yield value @@ -359,7 +359,7 @@ def _filter_operation_errors(self, infer_callable, context, error): # For the sake of .infer(), we don't care about operation # errors, which is the job of pylint. So return something # which shows that we can't infer the result. - yield util.YES + yield util.Uninferable else: yield result @@ -377,12 +377,12 @@ def _infer_unaryop(self, context=None): if meth is None: # `not node`. Determine node's boolean # value and negate its result, unless it is - # YES, which will be returned as is. + # Uninferable, which will be returned as is. bool_value = operand.bool_value() - if bool_value is not util.YES: + if bool_value is not util.Uninferable: yield nodes.const_factory(not bool_value) else: - yield util.YES + yield util.Uninferable else: if not isinstance(operand, bases.Instance): # The operation was used on something which @@ -393,7 +393,7 @@ def _infer_unaryop(self, context=None): try: meth = operand.getattr(meth, context=context)[0] inferred = next(meth.infer(context=context)) - if inferred is util.YES or not inferred.callable(): + if inferred is util.Uninferable or not inferred.callable(): continue context = contextmod.copy_context(context) @@ -409,7 +409,7 @@ def _infer_unaryop(self, context=None): # The unary operation special method was not found. yield util.BadUnaryOperationMessage(operand, self.op, exc) except exceptions.InferenceError: - yield util.YES + yield util.Uninferable @decorators.raise_if_nothing_inferred @@ -570,23 +570,23 @@ def _infer_binary_operation(left, right, op, context, flow_factory): except exceptions.AttributeInferenceError: continue except exceptions.InferenceError: - yield util.YES + yield util.Uninferable return else: - if any(result is util.YES for result in results): - yield util.YES + if any(result is util.Uninferable for result in results): + yield util.Uninferable return # TODO(cpopa): since the inferrence engine might return # more values than are actually possible, we decide - # to return util.YES if we have union types. + # to return util.Uninferable if we have union types. if all(map(_is_not_implemented, results)): continue not_implemented = sum(1 for result in results if _is_not_implemented(result)) if not_implemented and not_implemented != len(results): # Can't decide yet what this is, not yet though. - yield util.YES + yield util.Uninferable return for result in results: @@ -612,15 +612,15 @@ def _infer_binop(self, context): rhs_context = context.clone() for lhs in left.infer(context=lhs_context): - if lhs is util.YES: + if lhs is util.Uninferable: # Don't know how to process this. - yield util.YES + yield util.Uninferable return for rhs in right.infer(context=rhs_context): - if rhs is util.YES: + if rhs is util.Uninferable: # Don't know how to process this. - yield util.YES + yield util.Uninferable return results = _infer_binary_operation(lhs, rhs, op, @@ -646,9 +646,9 @@ def _infer_augassign(self, context=None): op = self.op for lhs in self.target.infer_lhs(context=context): - if lhs is util.YES: + if lhs is util.Uninferable: # Don't know how to process this. - yield util.YES + yield util.Uninferable return # TODO(cpopa): if we have A() * A(), trying to infer @@ -658,9 +658,9 @@ def _infer_augassign(self, context=None): rhs_context = context.clone() rhs_context.path = set() for rhs in self.value.infer(context=rhs_context): - if rhs is util.YES: + if rhs is util.Uninferable: # Don't know how to process this. - yield util.YES + yield util.Uninferable return results = _infer_binary_operation(lhs, rhs, op, @@ -708,14 +708,14 @@ nodes.AssignAttr._infer = infer_assign @decorators.path_wrapper def infer_empty_node(self, context=None): if not self.has_underlying_object(): - yield util.YES + yield util.Uninferable else: try: for inferred in MANAGER.infer_ast_from_something(self.object, context=context): yield inferred except exceptions.AstroidError: - yield util.YES + yield util.Uninferable nodes.EmptyNode._infer = infer_empty_node diff --git a/astroid/manager.py b/astroid/manager.py index c14125f..0f6673d 100644 --- a/astroid/manager.py +++ b/astroid/manager.py @@ -252,7 +252,7 @@ class AstroidManager(object): yield inferred else: for inferred in modastroid.igetattr(name, context): - yield inferred.instanciate_class() + yield inferred.instantiate_class() def register_failed_import_hook(self, hook): """Registers a hook to resolve imports that cannot be found otherwise. diff --git a/astroid/node_classes.py b/astroid/node_classes.py index 5a92210..3dbba34 100644 --- a/astroid/node_classes.py +++ b/astroid/node_classes.py @@ -56,9 +56,9 @@ def unpack_infer(stmt, context=None): if inferred is stmt: yield inferred return - # else, infer recursivly, except YES object that should be returned as is + # else, infer recursivly, except Uninferable object that should be returned as is for inferred in stmt.infer(context): - if inferred is util.YES: + if inferred is util.Uninferable: yield inferred else: for inf_inf in unpack_infer(inferred, context): @@ -426,8 +426,8 @@ class NodeNG(object): PendingDeprecationWarning, stacklevel=2) return self.inferred() - def instanciate_class(self): - """instanciate a node if it is a ClassDef node, else return self""" + def instantiate_class(self): + """instantiate a node if it is a ClassDef node, else return self""" return self def has_base(self, node): @@ -572,10 +572,10 @@ class NodeNG(object): method. * True. Most of constructs are True by default: classes, functions, modules etc - * YES: the inference engine is uncertain of the + * Uninferable: the inference engine is uncertain of the node's value. """ - return util.YES + return util.Uninferable class Statement(NodeNG): @@ -1292,7 +1292,7 @@ class Dict(NodeNG, bases.Instance): except IndexError: continue for inferredkey in key.infer(context): - if inferredkey is util.YES: + if inferredkey is util.Uninferable: continue if isinstance(inferredkey, Const) \ and inferredkey.value == lookup_key: diff --git a/astroid/protocols.py b/astroid/protocols.py index 5809e4a..6b6c0c0 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -127,12 +127,12 @@ def const_infer_binary_op(self, operator, other, context, _): # ArithmeticError is not enough: float >> float is a TypeError yield not_implemented except Exception: # pylint: disable=broad-except - yield util.YES + yield util.Uninferable except TypeError: yield not_implemented elif isinstance(self.value, six.string_types) and operator == '%': # TODO(cpopa): implement string interpolation later on. - yield util.YES + yield util.Uninferable else: yield not_implemented @@ -145,7 +145,7 @@ def _multiply_seq_by_int(self, other, context): for elt in self.elts: infered = helpers.safe_infer(elt, context) if infered is None: - infered = util.YES + infered = util.Uninferable elts.append(infered) node.elts = elts * other.value return node @@ -157,9 +157,9 @@ def tl_infer_binary_op(self, operator, other, context, method): if isinstance(other, self.__class__) and operator == '+': node = self.__class__() elts = [n for elt in self.elts for n in elt.infer(context) - if not n is util.YES] + if not n is util.Uninferable] elts += [n for elt in other.elts for n in elt.infer(context) - if not n is util.YES] + if not n is util.Uninferable] node.elts = elts yield node elif isinstance(other, nodes.Const) and operator == '*': @@ -171,7 +171,7 @@ def tl_infer_binary_op(self, operator, other, context, method): # Verify if the instance supports __index__. as_index = helpers.class_instance_as_index(other) if not as_index: - yield util.YES + yield util.Uninferable else: yield _multiply_seq_by_int(self, as_index, context) else: @@ -206,7 +206,7 @@ def _resolve_looppart(parts, asspath, context): asspath = asspath[:] index = asspath.pop(0) for part in parts: - if part is util.YES: + if part is util.Uninferable: continue # XXX handle __iter__ and log potentially detected errors if not hasattr(part, 'itered'): @@ -226,7 +226,7 @@ def _resolve_looppart(parts, asspath, context): # we achieved to resolved the assignment path, # don't infer the last part yield assigned - elif assigned is util.YES: + elif assigned is util.Uninferable: break else: # we are not yet on the last part of the path @@ -278,7 +278,7 @@ def _arguments_infer_argname(self, name, context): # arguments information may be missing, in which case we can't do anything # more if not (self.args or self.vararg or self.kwarg): - yield util.YES + yield util.Uninferable return # first argument of instance/class method if self.args and getattr(self.args[0], 'name', None) == name: @@ -307,15 +307,15 @@ def _arguments_infer_argname(self, name, context): kwarg.parent = self yield kwarg return - # if there is a default value, yield it. And then yield YES to reflect + # if there is a default value, yield it. And then yield Uninferable to reflect # we can't guess given argument value try: context = contextmod.copy_context(context) for inferred in self.default_value(name).infer(context): yield inferred - yield util.YES + yield util.Uninferable except exceptions.NoDefault: - yield util.YES + yield util.Uninferable def arguments_assigned_stmts(self, node, context, asspath=None): @@ -363,7 +363,7 @@ def _resolve_asspart(parts, asspath, context): # we achieved to resolved the assignment path, don't infer the # last part yield assigned - elif assigned is util.YES: + elif assigned is util.Uninferable: return else: # we are not yet on the last part of the path search on each @@ -523,11 +523,11 @@ def starred_assigned_stmts(self, node=None, context=None, asspath=None): try: rhs = next(value.infer(context)) except exceptions.InferenceError: - yield util.YES + yield util.Uninferable return - if rhs is util.YES or not hasattr(rhs, 'elts'): + if rhs is util.Uninferable or not hasattr(rhs, 'elts'): # Not interested in inferred values without elts. - yield util.YES + yield util.Uninferable return elts = collections.deque(rhs.elts[:]) diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index a994392..ab130f5 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -552,7 +552,7 @@ class DictComp(ComprehensionScope): self.generators = generators def bool_value(self): - return util.YES + return util.Uninferable class SetComp(ComprehensionScope): @@ -573,7 +573,7 @@ class SetComp(ComprehensionScope): self.generators = generators def bool_value(self): - return util.YES + return util.Uninferable class _ListComp(node_classes.NodeNG): @@ -587,7 +587,7 @@ class _ListComp(node_classes.NodeNG): self.generators = generators def bool_value(self): - return util.YES + return util.Uninferable if six.PY3: @@ -936,7 +936,7 @@ class FunctionDef(node_classes.Statement, Lambda): c.hide = True c.parent = self class_bases = [next(b.infer(context)) for b in caller.args[1:]] - c.bases = [base for base in class_bases if base != util.YES] + c.bases = [base for base in class_bases if base != util.Uninferable] c._metaclass = metaclass yield c return @@ -949,7 +949,7 @@ class FunctionDef(node_classes.Statement, Lambda): for inferred in returnnode.value.infer(context): yield inferred except exceptions.InferenceError: - yield util.YES + yield util.Uninferable def bool_value(self): return True @@ -990,7 +990,7 @@ def _is_metaclass(klass, seen=None): if isinstance(baseobj, bases.Instance): # not abstract return False - if baseobj is util.YES: + if baseobj is util.Uninferable: continue if baseobj is klass: continue @@ -1160,7 +1160,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, isinstance(name_node.value, six.string_types)): name = name_node.value else: - return util.YES + return util.Uninferable result = ClassDef(name, None) @@ -1170,9 +1170,9 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, result.bases = class_bases.itered() else: # There is currently no AST node that can represent an 'unknown' - # node (YES is not an AST node), therefore we simply return YES here + # node (Uninferable is not an AST node), therefore we simply return Uninferable here # although we know at least the name of the class. - return util.YES + return util.Uninferable # Get the members of the class try: @@ -1343,7 +1343,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, raise exceptions.AttributeInferenceError(target=self, attribute=name, context=context) - def instanciate_class(self): + def instantiate_class(self): """return Instance of ClassDef node, else return self""" return bases.Instance(self) @@ -1352,7 +1352,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, This method doesn't look in the instance_attrs dictionary since it's done by an Instance proxy at inference time. It - may return a YES object if the attribute has not been actually + may return a Uninferable object if the attribute has not been actually found but a __getattr__ or __getattribute__ method is defined. If *class_context* is given, then it's considered that the attribute is accessed from a class context, @@ -1442,7 +1442,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, try: for inferred in bases._infer_stmts(self.getattr(name, context), context, frame=self): - # yield YES object instead of descriptors when necessary + # yield Uninferable object instead of descriptors when necessary if (not isinstance(inferred, node_classes.Const) and isinstance(inferred, bases.Instance)): try: @@ -1450,13 +1450,13 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, except exceptions.AttributeInferenceError: yield inferred else: - yield util.YES + yield util.Uninferable else: yield function_to_method(inferred, self) 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 + # class handle some dynamic attributes, return a Uninferable object + yield util.Uninferable else: util.reraise(exceptions.InferenceError( error.message, target=self, attribute=name, context=context)) @@ -1538,7 +1538,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, # Expects this from Py3k TreeRebuilder try: return next(node for node in self._metaclass.infer() - if node is not util.YES) + if node is not util.Uninferable) except (exceptions.InferenceError, StopIteration): return None if six.PY3: @@ -1561,7 +1561,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, inferred = next(assignment.infer()) except exceptions.InferenceError: return - if inferred is util.YES: # don't expose this + if inferred is util.Uninferable: # don't expose this return None return inferred @@ -1620,7 +1620,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, values = [item[0] for item in slots.items] else: values = slots.itered() - if values is util.YES: + if values is util.Uninferable: continue if not values: # Stop the iteration, because the class @@ -1630,7 +1630,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, for elt in values: try: for inferred in elt.infer(): - if inferred is util.YES: + if inferred is util.Uninferable: continue if (not isinstance(inferred, node_classes.Const) or not isinstance(inferred.value, diff --git a/astroid/tests/unittest_brain.py b/astroid/tests/unittest_brain.py index ca47d52..87f4913 100644 --- a/astroid/tests/unittest_brain.py +++ b/astroid/tests/unittest_brain.py @@ -119,7 +119,7 @@ class NamedTupleTest(unittest.TestCase): def foo(fields): return __(namedtuple("foo", fields)) """) - self.assertIs(util.YES, next(klass.infer())) + self.assertIs(util.Uninferable, next(klass.infer())) @unittest.skipIf(sys.version_info[0] > 2, 'namedtuple inference is broken on Python 3') diff --git a/astroid/tests/unittest_builder.py b/astroid/tests/unittest_builder.py index 485963b..dc161d0 100644 --- a/astroid/tests/unittest_builder.py +++ b/astroid/tests/unittest_builder.py @@ -482,7 +482,7 @@ class BuilderTest(unittest.TestCase): n = test_utils.get_name_node(astroid, 'n') self.assertIsNot(n.scope(), astroid) self.assertEqual([i.__class__ for i in n.infer()], - [util.YES.__class__]) + [util.Uninferable.__class__]) def test_no_future_imports(self): mod = builder.parse("import sys") diff --git a/astroid/tests/unittest_helpers.py b/astroid/tests/unittest_helpers.py index 6225966..fc95d63 100644 --- a/astroid/tests/unittest_helpers.py +++ b/astroid/tests/unittest_helpers.py @@ -154,7 +154,7 @@ class TestHelpers(unittest.TestCase): from unknown import Unknown u = Unknown #@ ''') - self.assertEqual(helpers.object_type(node), util.YES) + self.assertEqual(helpers.object_type(node), util.Uninferable) def test_object_type_too_many_types(self): node = test_utils.extract_node(''' @@ -166,7 +166,7 @@ class TestHelpers(unittest.TestCase): return 1 test(Unknown) #@ ''') - self.assertEqual(helpers.object_type(node), util.YES) + self.assertEqual(helpers.object_type(node), util.Uninferable) def test_is_subtype(self): ast_nodes = test_utils.extract_node(''' @@ -216,8 +216,8 @@ class TestHelpers(unittest.TestCase): class F(D, E): pass #@ ''') self.assertFalse(helpers.is_subtype(cls_e, cls_f)) - self.assertEqual(helpers.is_subtype(cls_f, cls_e), util.YES) - self.assertEqual(helpers.is_supertype(cls_e, cls_f), util.YES) + self.assertEqual(helpers.is_subtype(cls_f, cls_e), util.Uninferable) + self.assertEqual(helpers.is_supertype(cls_e, cls_f), util.Uninferable) self.assertFalse(helpers.is_supertype(cls_f, cls_e)) def test_is_subtype_supertype_unknown_bases(self): diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py index 6adaada..19b8377 100644 --- a/astroid/tests/unittest_inference.py +++ b/astroid/tests/unittest_inference.py @@ -312,7 +312,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): self.assertIsInstance(obj1, nodes.Const) self.assertEqual(obj1.value, 0) obj1 = next(inferred) - self.assertIs(obj1, util.YES, obj1) + self.assertIs(obj1, util.Uninferable, obj1) self.assertRaises(StopIteration, partial(next, inferred)) def test_args_default_inference2(self): @@ -321,13 +321,13 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): self.assertIsInstance(obj1, nodes.Const) self.assertEqual(obj1.value, 4) obj1 = next(inferred) - self.assertIs(obj1, util.YES, obj1) + self.assertIs(obj1, util.Uninferable, obj1) self.assertRaises(StopIteration, partial(next, inferred)) def test_inference_restrictions(self): inferred = test_utils.get_name_node(self.ast['C']['meth1'], 'arg1').infer() obj1 = next(inferred) - self.assertIs(obj1, util.YES, obj1) + self.assertIs(obj1, util.Uninferable, obj1) self.assertRaises(StopIteration, partial(next, inferred)) def test_ancestors_inference(self): @@ -563,7 +563,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): ast = parse(code, __name__) xxx = ast['xxx'] self.assertSetEqual({n.__class__ for n in xxx.inferred()}, - {nodes.Const, util.YES.__class__}) + {nodes.Const, util.Uninferable.__class__}) def test_method_argument(self): code = ''' @@ -579,13 +579,13 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): ast = parse(code, __name__) arg = test_utils.get_name_node(ast['ErudiEntitySchema']['__init__'], 'e_type') self.assertEqual([n.__class__ for n in arg.infer()], - [util.YES.__class__]) + [util.Uninferable.__class__]) arg = test_utils.get_name_node(ast['ErudiEntitySchema']['__init__'], 'kwargs') self.assertEqual([n.__class__ for n in arg.infer()], [nodes.Dict]) arg = test_utils.get_name_node(ast['ErudiEntitySchema']['meth'], 'e_type') self.assertEqual([n.__class__ for n in arg.infer()], - [util.YES.__class__]) + [util.Uninferable.__class__]) arg = test_utils.get_name_node(ast['ErudiEntitySchema']['meth'], 'args') self.assertEqual([n.__class__ for n in arg.infer()], [nodes.Tuple]) @@ -722,7 +722,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): for node in ast_nodes[:3]: self.assertRaises(InferenceError, next, node.infer()) for node in ast_nodes[3:]: - self.assertEqual(next(node.infer()), util.YES) + self.assertEqual(next(node.infer()), util.Uninferable) ast_nodes = test_utils.extract_node(''' [1, 2, 3][None] #@ 'lala'['bala'] #@ @@ -971,7 +971,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): self.assertEqual(first.value, 43) second = next(ast_nodes[1].infer()) - self.assertEqual(second, util.YES) + self.assertEqual(second, util.Uninferable) def test_binary_op_other_type_using_reflected_operands(self): ast_nodes = test_utils.extract_node(''' @@ -982,7 +982,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): 1 + A() #@ ''') first = next(ast_nodes[0].infer()) - self.assertEqual(first, util.YES) + self.assertEqual(first, util.Uninferable) second = next(ast_nodes[1].infer()) self.assertIsInstance(second, nodes.Const) @@ -996,7 +996,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): 1 + A() #@ ''') first = next(ast_node.infer()) - self.assertEqual(first, util.YES) + self.assertEqual(first, util.Uninferable) def test_binary_op_list_mul(self): for code in ('a = [[]] * 2', 'a = 2 * [[]]'): @@ -1013,10 +1013,10 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): ast = builder.string_build('a = [1] * None\nb = [1] * "r"') inferred = ast['a'].inferred() self.assertEqual(len(inferred), 1) - self.assertEqual(inferred[0], util.YES) + self.assertEqual(inferred[0], util.Uninferable) inferred = ast['b'].inferred() self.assertEqual(len(inferred), 1) - self.assertEqual(inferred[0], util.YES) + self.assertEqual(inferred[0], util.Uninferable) def test_binary_op_list_mul_int(self): 'test correct handling on list multiplied by int when there are more than one' @@ -1088,7 +1088,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): callfuncnode = test_utils.extract_node(code) inferred = list(callfuncnode.infer()) self.assertEqual(len(inferred), 2, inferred) - inferred.remove(util.YES) + inferred.remove(util.Uninferable) self.assertIsInstance(inferred[0], nodes.Const) self.assertIsNone(inferred[0].value) @@ -1100,7 +1100,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): ast = parse(code, __name__) inferred = list(ast['f'].ilookup('a')) self.assertEqual(len(inferred), 1) - self.assertEqual(inferred[0], util.YES) + self.assertEqual(inferred[0], util.Uninferable) def test_nonregr_instance_attrs(self): """non regression for instance_attrs infinite loop : pylint / #4""" @@ -1152,7 +1152,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): self.assertTrue(ast.absolute_import_activated(), True) inferred = next(test_utils.get_name_node(ast, 'import_package_subpackage_module').infer()) # failed to import since absolute_import is activated - self.assertIs(inferred, util.YES) + self.assertIs(inferred, util.Uninferable) def test_nonregr_absolute_import(self): ast = resources.build_file('data/absimp/string.py', 'data.absimp.string') @@ -1270,7 +1270,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): ast = parse(code, __name__) inferred = list(test_utils.get_name_node(ast['foo'], 'spam').infer()) self.assertEqual(len(inferred), 1) - self.assertIs(inferred[0], util.YES) + self.assertIs(inferred[0], util.Uninferable) def test_nonregr_func_global(self): code = ''' @@ -1461,7 +1461,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): ast = parse(code, __name__) sub = ast['sub'].inferred()[0] mul = ast['mul'].inferred()[0] - self.assertIs(sub, util.YES) + self.assertIs(sub, util.Uninferable) self.assertIsInstance(mul, nodes.Const) self.assertEqual(mul.value, 42) @@ -1480,7 +1480,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): ast = parse(code, __name__) sub = ast['sub'].inferred()[0] mul = ast['mul'].inferred()[0] - self.assertIs(sub, util. YES) + self.assertIs(sub, util. Uninferable) self.assertIsInstance(mul, nodes.Const) self.assertEqual(mul.value, 42) @@ -1500,7 +1500,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): ast = parse(code, __name__) sub = ast['sub'].inferred()[0] mul = ast['mul'].inferred()[0] - self.assertIs(sub, util.YES) + self.assertIs(sub, util.Uninferable) self.assertIsInstance(mul, nodes.List) self.assertIsInstance(mul.elts[0], nodes.Const) self.assertEqual(mul.elts[0].value, 42) @@ -1517,12 +1517,12 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): """ ast = parse(code, __name__) node = ast['c'] - self.assertEqual(node.inferred(), [util.YES]) + self.assertEqual(node.inferred(), [util.Uninferable]) def test_infer_empty_nodes(self): # Should not crash when trying to infer EmptyNodes. node = nodes.EmptyNode() - self.assertEqual(node.inferred(), [util.YES]) + self.assertEqual(node.inferred(), [util.Uninferable]) def test_infinite_loop_for_decorators(self): # Issue https://bitbucket.org/logilab/astroid/issue/50 @@ -2016,7 +2016,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): def test_unary_op_leaks_stop_iteration(self): node = test_utils.extract_node('+[] #@') - self.assertEqual(util.YES, next(node.infer())) + self.assertEqual(util.Uninferable, next(node.infer())) def test_unary_operands(self): ast_nodes = test_utils.extract_node(''' @@ -2068,7 +2068,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): for bad_node in ast_nodes[6:]: inferred = next(bad_node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_unary_op_instance_method_not_callable(self): ast_node = test_utils.extract_node(''' @@ -2265,11 +2265,11 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): genexpr = next(module['genexpr'].infer()) self.assertTrue(genexpr.bool_value()) dict_comp = next(module['dict_comp'].infer()) - self.assertEqual(dict_comp, util.YES) + self.assertEqual(dict_comp, util.Uninferable) set_comp = next(module['set_comp'].infer()) - self.assertEqual(set_comp, util.YES) + self.assertEqual(set_comp, util.Uninferable) list_comp = next(module['list_comp'].infer()) - self.assertEqual(list_comp, util.YES) + self.assertEqual(list_comp, util.Uninferable) lambda_func = next(module['lambda_func'].infer()) self.assertTrue(lambda_func) unbound_method = next(module['unbound_method'].infer()) @@ -2283,13 +2283,13 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): bin_op = module['bin_op'].parent.value self.assertTrue(bin_op.bool_value()) bool_op = module['bool_op'].parent.value - self.assertEqual(bool_op.bool_value(), util.YES) + self.assertEqual(bool_op.bool_value(), util.Uninferable) callfunc = module['callfunc'].parent.value - self.assertEqual(callfunc.bool_value(), util.YES) + self.assertEqual(callfunc.bool_value(), util.Uninferable) good_callfunc = next(module['good_callfunc'].infer()) self.assertTrue(good_callfunc.bool_value()) compare = module['compare'].parent.value - self.assertEqual(compare.bool_value(), util.YES) + self.assertEqual(compare.bool_value(), util.Uninferable) def test_bool_value_instances(self): instances = test_utils.extract_node(''' @@ -2322,7 +2322,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): AlwaysTrueInstance() #@ ErrorInstance() #@ '''.format(bool=BOOL_SPECIAL_METHOD)) - expected = (False, True, False, True, True, util.YES, util.YES) + expected = (False, True, False, True, True, util.Uninferable, util.Uninferable) for node, expected_value in zip(instances, expected): inferred = next(node.infer()) self.assertEqual(inferred.bool_value(), expected_value) @@ -2394,7 +2394,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): A() + B() #@ ''') inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_binop_different_types_reflected_and_normal_not_implemented(self): node = test_utils.extract_node(''' @@ -2405,7 +2405,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): A() + B() #@ ''') inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_binop_subtype(self): node = test_utils.extract_node(''' @@ -2438,7 +2438,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): B() + A() #@ ''') inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_binop_supertype(self): node = test_utils.extract_node(''' @@ -2477,7 +2477,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): A() + B() #@ ''') inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_binop_inferrence_errors(self): ast_nodes = test_utils.extract_node(''' @@ -2492,7 +2492,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): A() + B() #@ ''') for node in ast_nodes: - self.assertEqual(next(node.infer()), util.YES) + self.assertEqual(next(node.infer()), util.Uninferable) def test_binop_ambiguity(self): ast_nodes = test_utils.extract_node(''' @@ -2515,7 +2515,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): C() + A() #@ ''') for node in ast_nodes: - self.assertEqual(next(node.infer()), util.YES) + self.assertEqual(next(node.infer()), util.Uninferable) def test_bin_op_supertype_more_complicated_example(self): ast_node = test_utils.extract_node(''' @@ -2544,7 +2544,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): def __add__(self, other): return NotImplemented A() + A() #@ ''') - self.assertEqual(next(ast_node.infer()), util.YES) + self.assertEqual(next(ast_node.infer()), util.Uninferable) def test_aug_op_same_type_aug_implemented(self): ast_node = test_utils.extract_node(''' @@ -2579,7 +2579,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): b = B() b+=A() #@ ''') - self.assertEqual(next(ast_node.infer()), util.YES) + self.assertEqual(next(ast_node.infer()), util.Uninferable) def test_aug_op_subtype_aug_op_is_implemented(self): ast_node = test_utils.extract_node(''' @@ -2614,7 +2614,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): f = A() f += B() #@ ''') - self.assertEqual(next(ast_node.infer()), util.YES) + self.assertEqual(next(ast_node.infer()), util.Uninferable) def test_aug_different_types_augop_implemented(self): ast_node = test_utils.extract_node(''' @@ -2662,7 +2662,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): a = A() a += B() #@ ''') - self.assertEqual(next(ast_node.infer()), util.YES) + self.assertEqual(next(ast_node.infer()), util.Uninferable) def test_augop_supertypes_not_implemented_returned_for_all(self): ast_node = test_utils.extract_node(''' @@ -2674,7 +2674,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): a = A() a += B() #@ ''') - self.assertEqual(next(ast_node.infer()), util.YES) + self.assertEqual(next(ast_node.infer()), util.Uninferable) def test_augop_supertypes_augop_implemented(self): ast_node = test_utils.extract_node(''' @@ -2746,7 +2746,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): [1, 2, 1, 2]) for rest in ast_nodes[1:]: inferred = next(rest.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_subscript_supports__index__(self): ast_nodes = test_utils.extract_node(''' @@ -2915,7 +2915,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): A()[2:] #@ ''') for node in ast_nodes: - self.assertEqual(next(node.infer()), util.YES) + self.assertEqual(next(node.infer()), util.Uninferable) def test_type__new__with_metaclass(self): ast_node = test_utils.extract_node(''' @@ -3158,7 +3158,7 @@ class GetattrTest(unittest.TestCase): for node in ast_nodes[4:]: inferred = next(node.infer()) - self.assertEqual(inferred, util.YES, node) + self.assertEqual(inferred, util.Uninferable, node) def test_attrname_not_string(self): ast_nodes = test_utils.extract_node(''' @@ -3246,7 +3246,7 @@ class GetattrTest(unittest.TestCase): getattr(lambda x: x, 'f') #@ ''') inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) class HasattrTest(unittest.TestCase): @@ -3262,7 +3262,7 @@ class HasattrTest(unittest.TestCase): ''') for node in ast_nodes: inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_attribute_is_missing(self): ast_nodes = test_utils.extract_node(''' @@ -3305,7 +3305,7 @@ class HasattrTest(unittest.TestCase): hasattr(lambda x: x, 'f') #@ ''') inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) class BoolOpTest(unittest.TestCase): @@ -3342,7 +3342,7 @@ class BoolOpTest(unittest.TestCase): ''') for node in ast_nodes: inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_other_nodes(self): ast_nodes = test_utils.extract_node(''' @@ -3422,7 +3422,7 @@ class TestCallable(unittest.TestCase): ''') for node in ast_nodes: inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_not_callable(self): ast_nodes = test_utils.extract_node(''' @@ -3448,12 +3448,12 @@ class TestBool(unittest.TestCase): ('bool(True)', True), ('bool(False)', False), ('bool(None)', False), - ('from unknown import Unknown; __(bool(Unknown))', util.YES), + ('from unknown import Unknown; __(bool(Unknown))', util.Uninferable), ] for code, expected in pairs: node = test_utils.extract_node(code) inferred = next(node.infer()) - if expected is util.YES: + if expected is util.Uninferable: self.assertEqual(expected, inferred) else: self.assertEqual(inferred.value, expected) @@ -3503,7 +3503,7 @@ class TestBool(unittest.TestCase): '''.format(method=BOOL_SPECIAL_METHOD)) for node in ast_nodes: inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) class TestType(unittest.TestCase): @@ -3715,7 +3715,7 @@ class ArgumentsTest(unittest.TestCase): ''') for ast_node in ast_nodes: inferred = next(ast_node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) def test_fail_to_infer_args(self): ast_nodes = test_utils.extract_node(''' @@ -3745,7 +3745,7 @@ class ArgumentsTest(unittest.TestCase): ''') for node in ast_nodes: inferred = next(node.infer()) - self.assertEqual(inferred, util.YES) + self.assertEqual(inferred, util.Uninferable) class SliceTest(unittest.TestCase): diff --git a/astroid/tests/unittest_lookup.py b/astroid/tests/unittest_lookup.py index 7f9c43a..c91acf7 100644 --- a/astroid/tests/unittest_lookup.py +++ b/astroid/tests/unittest_lookup.py @@ -172,7 +172,7 @@ class LookupTest(resources.SysPathSetup, unittest.TestCase): """) var = astroid.body[1].value if sys.version_info < (3, 0): - self.assertEqual(var.inferred(), [util.YES]) + self.assertEqual(var.inferred(), [util.Uninferable]) else: self.assertRaises(exceptions.NameInferenceError, var.inferred) diff --git a/astroid/tests/unittest_nodes.py b/astroid/tests/unittest_nodes.py index 55c7268..3677bfb 100644 --- a/astroid/tests/unittest_nodes.py +++ b/astroid/tests/unittest_nodes.py @@ -359,7 +359,7 @@ from ..cave import wine\n\n""" method of this From node will be made by unpack_infer. inference.infer_from will try to import this module, which will fail and raise a InferenceException (by mixins.do_import_module). The infer_name - will catch this exception and yield and YES instead. + will catch this exception and yield and Uninferable instead. ''' code = ''' @@ -383,7 +383,7 @@ from ..cave import wine\n\n""" # present in the other version. self.assertIsInstance(excs[0], nodes.ClassDef) self.assertEqual(excs[0].name, 'PickleError') - self.assertIs(excs[-1], util.YES) + self.assertIs(excs[-1], util.Uninferable) def test_absolute_import(self): astroid = resources.build_file('data/absimport.py') diff --git a/astroid/tests/unittest_protocols.py b/astroid/tests/unittest_protocols.py index 9ba4f6a..6d6bce6 100644 --- a/astroid/tests/unittest_protocols.py +++ b/astroid/tests/unittest_protocols.py @@ -67,7 +67,7 @@ class ProtocolTests(unittest.TestCase): for1_starred = next(assign_stmts.nodes_of_class(Starred)) assigned = next(for1_starred.assigned_stmts()) - self.assertEqual(assigned, util.YES) + self.assertEqual(assigned, util.Uninferable) def _get_starred_stmts(self, code): assign_stmt = extract_node("{} #@".format(code)) @@ -108,16 +108,16 @@ class ProtocolTests(unittest.TestCase): @require_version(minver='3.0') def test_assigned_stmts_starred_yes(self): # Not something iterable and known - self._helper_starred_expected("a, *b = range(3) #@", util.YES) + self._helper_starred_expected("a, *b = range(3) #@", util.Uninferable) # Not something inferrable - self._helper_starred_expected("a, *b = balou() #@", util.YES) + self._helper_starred_expected("a, *b = balou() #@", util.Uninferable) # In function, unknown. self._helper_starred_expected(""" def test(arg): - head, *tail = arg #@""", util.YES) + head, *tail = arg #@""", util.Uninferable) # These cases aren't worth supporting. self._helper_starred_expected( - "a, (*b, c), d = (1, (2, 3, 4), 5) #@", util.YES) + "a, (*b, c), d = (1, (2, 3, 4), 5) #@", util.Uninferable) @require_version(minver='3.0') def test_assign_stmts_starred_fails(self): diff --git a/astroid/tests/unittest_regrtest.py b/astroid/tests/unittest_regrtest.py index 3e22152..8e60e8a 100644 --- a/astroid/tests/unittest_regrtest.py +++ b/astroid/tests/unittest_regrtest.py @@ -251,7 +251,7 @@ def test(): def test_ancestors_yes_in_bases(self): # Test for issue https://bitbucket.org/logilab/astroid/issue/84 - # This used to crash astroid with a TypeError, because an YES + # This used to crash astroid with a TypeError, because an Uninferable # node was present in the bases node = extract_node(""" def with_metaclass(meta, *bases): diff --git a/astroid/tests/unittest_scoped_nodes.py b/astroid/tests/unittest_scoped_nodes.py index 47807aa..ddfb7a2 100644 --- a/astroid/tests/unittest_scoped_nodes.py +++ b/astroid/tests/unittest_scoped_nodes.py @@ -1132,7 +1132,7 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase): instance = astroid['tgts'] # used to raise "'_Yes' object is not iterable", see # https://bitbucket.org/logilab/astroid/issue/17 - self.assertEqual(list(instance.infer()), [util.YES]) + self.assertEqual(list(instance.infer()), [util.Uninferable]) def test_slots(self): astroid = builder.parse(""" @@ -1372,7 +1372,7 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase): class A(object): pass ''') - instance = cls.instanciate_class() + instance = cls.instantiate_class() func = cls.getattr('mro') self.assertEqual(len(func), 1) self.assertRaises(AttributeInferenceError, instance.getattr, 'mro') @@ -1395,7 +1395,7 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase): class B(object): pass ''') cls = module['B'] - self.assertEqual(util.YES, next(cls.igetattr('foo'))) + self.assertEqual(util.Uninferable, next(cls.igetattr('foo'))) def test_metaclass_lookup(self): module = builder.parse(''' diff --git a/astroid/util.py b/astroid/util.py index 90ea7d6..10c5415 100644 --- a/astroid/util.py +++ b/astroid/util.py @@ -33,10 +33,10 @@ def reraise(exception): @object.__new__ -class YES(object): +class Uninferable(object): """Special inference object, which is returned when inference fails.""" def __repr__(self): - return 'YES' + return 'Uninferable' def __getattribute__(self, name): if name == 'next': -- cgit v1.2.1