diff options
-rw-r--r-- | astroid/decorators.py | 63 | ||||
-rw-r--r-- | astroid/exceptions.py | 12 | ||||
-rw-r--r-- | astroid/inference.py | 28 | ||||
-rw-r--r-- | astroid/protocols.py | 27 | ||||
-rw-r--r-- | 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""" |