diff options
Diffstat (limited to 'astroid/inference.py')
-rw-r--r-- | astroid/inference.py | 339 |
1 files changed, 129 insertions, 210 deletions
diff --git a/astroid/inference.py b/astroid/inference.py index 30e02288..05cbe5eb 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -22,111 +22,17 @@ import functools import itertools import operator +from astroid import bases +from astroid import context as contextmod +from astroid import exceptions from astroid import helpers +from astroid import manager from astroid import nodes from astroid import protocols -from astroid.manager import AstroidManager -from astroid.exceptions import ( - AstroidError, InferenceError, NoDefault, - NotFoundError, UnresolvableName, - UnaryOperationError, - BinaryOperationError, -) -from astroid.bases import (YES, Instance, InferenceContext, BoundMethod, - _infer_stmts, copy_context, path_wrapper, - raise_if_nothing_inferred, yes_if_nothing_inferred) - -MANAGER = AstroidManager() - - -class CallContext(object): - """when inferring a function call, this class is used to remember values - given as argument - """ - def __init__(self, args, starargs, dstarargs): - self.args = [] - self.nargs = {} - for arg in args: - if isinstance(arg, nodes.Keyword): - self.nargs[arg.arg] = arg.value - else: - self.args.append(arg) - self.starargs = starargs - self.dstarargs = dstarargs +from astroid import util - def infer_argument(self, funcnode, name, context): - """infer a function argument value according to the call context""" - # 1. search in named keywords - try: - return self.nargs[name].infer(context) - except KeyError: - # Function.args.args can be None in astroid (means that we don't have - # information on argnames) - argindex = funcnode.args.find_argname(name)[0] - if argindex is not None: - # 2. first argument of instance/class method - if argindex == 0 and funcnode.type in ('method', 'classmethod'): - if context.boundnode is not None: - boundnode = context.boundnode - else: - # XXX can do better ? - boundnode = funcnode.parent.frame() - if funcnode.type == 'method': - if not isinstance(boundnode, Instance): - boundnode = Instance(boundnode) - return iter((boundnode,)) - if funcnode.type == 'classmethod': - return iter((boundnode,)) - # if we have a method, extract one position - # from the index, so we'll take in account - # the extra parameter represented by `self` or `cls` - if funcnode.type in ('method', 'classmethod'): - argindex -= 1 - # 2. search arg index - try: - return self.args[argindex].infer(context) - except IndexError: - pass - # 3. search in *args (.starargs) - if self.starargs is not None: - its = [] - for inferred in self.starargs.infer(context): - if inferred is YES: - its.append((YES,)) - continue - try: - its.append(inferred.getitem(argindex, context).infer(context)) - except (InferenceError, AttributeError): - its.append((YES,)) - except (IndexError, TypeError): - continue - if its: - return itertools.chain(*its) - # 4. XXX search in **kwargs (.dstarargs) - if self.dstarargs is not None: - its = [] - for inferred in self.dstarargs.infer(context): - if inferred is YES: - its.append((YES,)) - continue - try: - its.append(inferred.getitem(name, context).infer(context)) - except (InferenceError, AttributeError): - its.append((YES,)) - except (IndexError, TypeError): - continue - if its: - return itertools.chain(*its) - # 5. */** argument, (Tuple or Dict) - if name == funcnode.args.vararg: - return iter((nodes.const_factory(()))) - if name == funcnode.args.kwarg: - return iter((nodes.const_factory({}))) - # 6. return default value if any - try: - return funcnode.args.default_value(name).infer(context) - except NoDefault: - raise InferenceError(name) + +MANAGER = manager.AstroidManager() # .infer method ############################################################### @@ -176,72 +82,82 @@ def infer_name(self, context=None): _, stmts = parent_function.lookup(self.name) if not stmts: - raise UnresolvableName(self.name) + raise exceptions.UnresolvableName(self.name) context = context.clone() context.lookupname = self.name - return _infer_stmts(stmts, context, frame) -nodes.Name._infer = path_wrapper(infer_name) + return bases._infer_stmts(stmts, context, frame) +nodes.Name._infer = bases.path_wrapper(infer_name) nodes.AssignName.infer_lhs = infer_name # won't work with a path wrapper +@bases.raise_if_nothing_infered +@bases.path_wrapper def infer_callfunc(self, context=None): """infer a CallFunc node by trying to guess what the function returns""" callcontext = context.clone() - callcontext.callcontext = CallContext(self.args, self.starargs, self.kwargs) + callcontext.callcontext = contextmod.CallContext(args=self.args, + keywords=self.keywords, + starargs=self.starargs, + kwargs=self.kwargs) callcontext.boundnode = None for callee in self.func.infer(context): - if callee is YES: + if callee is util.YES: yield callee continue try: if hasattr(callee, 'infer_call_result'): for inferred in callee.infer_call_result(self, callcontext): yield inferred - except InferenceError: + except exceptions.InferenceError: ## XXX log error ? continue -nodes.Call._infer = path_wrapper(raise_if_nothing_inferred(infer_callfunc)) +nodes.Call._infer = infer_callfunc +@bases.path_wrapper 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 InferenceError() + raise exceptions.InferenceError() if asname: yield self.do_import_module(self.real_name(name)) else: yield self.do_import_module(name) -nodes.Import._infer = path_wrapper(infer_import) +nodes.Import._infer = infer_import + def infer_name_module(self, name): - context = InferenceContext() + context = contextmod.InferenceContext() context.lookupname = name return self.infer(context, asname=False) nodes.Import.infer_name_module = infer_name_module +@bases.path_wrapper def infer_from(self, context=None, asname=True): """infer a From nodes: return the imported module/object""" name = context.lookupname if name is None: - raise InferenceError() + raise exceptions.InferenceError() if asname: name = self.real_name(name) module = self.do_import_module() try: - context = copy_context(context) + context = contextmod.copy_context(context) context.lookupname = name - return _infer_stmts(module.getattr(name, ignore_locals=module is self.root()), context) - except NotFoundError: - raise InferenceError(name) -nodes.ImportFrom._infer = path_wrapper(infer_from) + stmts = module.getattr(name, ignore_locals=module is self.root()) + return bases._infer_stmts(stmts, context) + except exceptions.NotFoundError: + raise exceptions.InferenceError(name) +nodes.ImportFrom._infer = infer_from +@bases.raise_if_nothing_infered def infer_getattr(self, context=None): """infer a Getattr node by using getattr on the associated object""" for owner in self.expr.infer(context): - if owner is YES: + if owner is util.YES: yield owner continue try: @@ -249,61 +165,63 @@ def infer_getattr(self, context=None): for obj in owner.igetattr(self.attrname, context): yield obj context.boundnode = None - except (NotFoundError, InferenceError): + except (exceptions.NotFoundError, exceptions.InferenceError): context.boundnode = None except AttributeError: # XXX method / function context.boundnode = None -nodes.Attribute._infer = path_wrapper(raise_if_nothing_inferred(infer_getattr)) -nodes.AssignAttr.infer_lhs = raise_if_nothing_inferred(infer_getattr) # # won't work with a path wrapper +nodes.Attribute._infer = bases.path_wrapper(infer_getattr) +nodes.AssignAttr.infer_lhs = infer_getattr # # won't work with a path wrapper +@bases.path_wrapper def infer_global(self, context=None): if context.lookupname is None: - raise InferenceError() + raise exceptions.InferenceError() try: - return _infer_stmts(self.root().getattr(context.lookupname), context) - except NotFoundError: - raise InferenceError() -nodes.Global._infer = path_wrapper(infer_global) + return bases._infer_stmts(self.root().getattr(context.lookupname), + context) + except exceptions.NotFoundError: + raise exceptions.InferenceError() +nodes.Global._infer = infer_global def infer_subscript(self, context=None): """infer simple subscription such as [1,2,3][0] or (1,2,3)[-1]""" value = next(self.value.infer(context)) - if value is YES: - yield YES + if value is util.YES: + yield util.YES return index = next(self.slice.infer(context)) - if index is YES: - yield YES + if index is util.YES: + yield util.YES return if isinstance(index, nodes.Const): try: assigned = value.getitem(index.value, context) except AttributeError: - raise InferenceError() + raise exceptions.InferenceError() except (IndexError, TypeError): - yield YES + yield util.YES return # Prevent inferring if the inferred subscript # is the same as the original subscripted object. - if self is assigned or assigned is YES: - yield YES + if self is assigned or assigned is util.YES: + yield util.YES return for inferred in assigned.infer(context): yield inferred else: - raise InferenceError() -nodes.Subscript._infer = path_wrapper(infer_subscript) -nodes.Subscript.infer_lhs = raise_if_nothing_inferred(infer_subscript) + raise exceptions.InferenceError() +nodes.Subscript._infer = bases.path_wrapper(infer_subscript) +nodes.Subscript.infer_lhs = bases.raise_if_nothing_infered(infer_subscript) -@raise_if_nothing_inferred -@path_wrapper +@bases.raise_if_nothing_inferred +@bases.path_wrapper def _infer_boolop(self, context=None): """Infer a boolean operation (and / or / not). @@ -312,30 +230,29 @@ def _infer_boolop(self, context=None): node. """ values = self.values - op = self.op + if self.op == 'or': + predicate = operator.truth + else: + predicate = operator.not_ + try: values = [value.infer(context=context) for value in values] - except InferenceError: - yield YES + except exceptions.InferenceError: + yield util.YES return for pair in itertools.product(*values): - if any(item is YES for item in pair): + if any(item is util.YES for item in pair): # Can't infer the final result, just yield YES. - yield YES + yield util.YES continue bool_values = [item.bool_value() for item in pair] - if any(item is YES for item in bool_values): + if any(item is util.YES for item in bool_values): # Can't infer the final result, just yield YES. - yield YES + yield util.YES continue - if op == 'or': - predicate = operator.truth - else: - predicate = operator.not_ - # 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 @@ -345,7 +262,7 @@ def _infer_boolop(self, context=None): # 0 and 1 -> 0 # 1 or 0 -> 1 # 0 or 1 -> 1 - value = YES + value = util.YES for value, bool_value in zip(pair, bool_values): if predicate(bool_value): yield value @@ -364,7 +281,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 YES + yield util.YES else: yield result @@ -376,7 +293,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 UnaryOperationError(operand, self.op, exc) + yield exceptions.UnaryOperationError(operand, self.op, exc) except AttributeError as exc: meth = protocols.UNARY_OP_METHOD[self.op] if meth is None: @@ -384,15 +301,15 @@ def _infer_unaryop(self, context=None): # value and negate its result, unless it is # YES, which will be returned as is. bool_value = operand.bool_value() - if bool_value is not YES: + if bool_value is not util.YES: yield nodes.const_factory(not bool_value) else: - yield YES + yield util.YES else: - if not isinstance(operand, Instance): + if not isinstance(operand, bases.Instance): # The operation was used on something which # doesn't support it. - yield UnaryOperationError(operand, self.op, exc) + yield exceptions.UnaryOperationError(operand, self.op, exc) continue try: @@ -405,21 +322,21 @@ def _infer_unaryop(self, context=None): yield operand else: yield result - except NotFoundError as exc: + except exceptions.NotFoundError as exc: # The unary operation special method was not found. - yield UnaryOperationError(operand, self.op, exc) - except InferenceError: - yield YES + yield exceptions.UnaryOperationError(operand, self.op, exc) + except exceptions.InferenceError: + yield util.YES -@path_wrapper +@bases.path_wrapper def infer_unaryop(self, context=None): """Infer what an UnaryOp should return when evaluated.""" - return _filter_operation_errors(self, _infer_unaryop, - context, UnaryOperationError) + return _filter_operation_errors(self, _infer_unaryop, context, + exceptions.UnaryOperationError) nodes.UnaryOp._infer_unaryop = _infer_unaryop -nodes.UnaryOp._infer = raise_if_nothing_inferred(infer_unaryop) +nodes.UnaryOp._infer = bases.raise_if_nothing_inferred(infer_unaryop) def _is_not_implemented(const): @@ -433,6 +350,7 @@ def _invoke_binop_inference(instance, op, other, context, method_name): inferred = next(method.infer(context=context)) return instance.infer_binary_op(op, other, context, inferred) + def _aug_op(instance, op, other, context, reverse=False): """Get an inference callable for an augmented binary operation.""" method_name = protocols.AUGMENTED_OP_METHOD[op] @@ -442,6 +360,7 @@ def _aug_op(instance, op, other, context, reverse=False): context=context, method_name=method_name) + def _bin_op(instance, op, other, context, reverse=False): """Get an inference callable for a normal binary operation. @@ -469,11 +388,11 @@ def _get_binop_contexts(context, left, right): # left.__op__(right). for arg in (right, left): new_context = context.clone() - new_context.callcontext = CallContext( - [arg], starargs=None, dstarargs=None) + new_context.callcontext = contextmod.CallContext(args=[arg]) new_context.boundnode = None yield new_context + def _same_type(type1, type2): """Check if type1 is the same as type2.""" return type1.qname() == type2.qname() @@ -560,26 +479,26 @@ def _infer_binary_operation(left, right, op, context, flow_factory): results = list(method()) except AttributeError: continue - except NotFoundError: + except exceptions.NotFoundError: continue - except InferenceError: - yield YES + except exceptions.InferenceError: + yield util.YES return else: - if any(result is YES for result in results): - yield YES + if any(result is util.YES for result in results): + yield util.YES return # TODO(cpopa): since the inferrence engine might return # more values than are actually possible, we decide - # to return YES if we have union types. + # to return util.YES 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 YES + yield util.YES return for result in results: @@ -587,21 +506,21 @@ def _infer_binary_operation(left, right, op, context, flow_factory): return # TODO(cpopa): yield a BinaryOperationError here, # since the operation is not supported - yield BinaryOperationError(left_type, op, right_type) + yield exceptions.BinaryOperationError(left_type, op, right_type) def _infer_binop(self, context): """Binary operation inferrence logic.""" if context is None: - context = InferenceContext() + context = contextmod.InferenceContext() left = self.left right = self.right op = self.op for lhs in left.infer(context=context): - if lhs is YES: + if lhs is util.YES: # Don't know how to process this. - yield YES + yield util.YES return # TODO(cpopa): if we have A() * A(), trying to infer @@ -611,9 +530,9 @@ def _infer_binop(self, context): rhs_context = context.clone() rhs_context.path = set() for rhs in right.infer(context=rhs_context): - if rhs is YES: + if rhs is util.YES: # Don't know how to process this. - yield YES + yield util.YES return results = _infer_binary_operation(lhs, rhs, op, @@ -622,25 +541,25 @@ def _infer_binop(self, context): yield result -@path_wrapper +@bases.path_wrapper def infer_binop(self, context=None): - return _filter_operation_errors(self, _infer_binop, - context, BinaryOperationError) + return _filter_operation_errors(self, _infer_binop, context, + exceptions.BinaryOperationError) nodes.BinOp._infer_binop = _infer_binop -nodes.BinOp._infer = yes_if_nothing_inferred(infer_binop) +nodes.BinOp._infer = bases.yes_if_nothing_inferred(infer_binop) def _infer_augassign(self, context=None): """Inferrence logic for augmented binary operations.""" if context is None: - context = InferenceContext() + context = contextmod.InferenceContext() op = self.op for lhs in self.target.infer_lhs(context=context): - if lhs is YES: + if lhs is util.YES: # Don't know how to process this. - yield YES + yield util.YES return # TODO(cpopa): if we have A() * A(), trying to infer @@ -650,9 +569,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 YES: + if rhs is util.YES: # Don't know how to process this. - yield YES + yield util.YES return results = _infer_binary_operation(lhs, rhs, op, @@ -661,10 +580,10 @@ def _infer_augassign(self, context=None): yield result -@path_wrapper +@bases.path_wrapper def infer_augassign(self, context=None): - return _filter_operation_errors(self, _infer_augassign, - context, BinaryOperationError) + return _filter_operation_errors(self, _infer_augassign, context, + exceptions.BinaryOperationError) nodes.AugAssign._infer_augassign = _infer_augassign nodes.AugAssign._infer = infer_augassign @@ -675,11 +594,12 @@ nodes.AugAssign._infer = infer_augassign def infer_arguments(self, context=None): name = context.lookupname if name is None: - raise InferenceError() + raise exceptions.InferenceError() return protocols._arguments_infer_argname(self, name, context) nodes.Arguments._infer = infer_arguments +@bases.path_wrapper def infer_ass(self, context=None): """infer a AssName/AssAttr: need to inspect the RHS part of the assign node @@ -688,25 +608,25 @@ def infer_ass(self, context=None): if isinstance(stmt, nodes.AugAssign): return stmt.infer(context) stmts = list(self.assigned_stmts(context=context)) - return _infer_stmts(stmts, context) -nodes.AssignName._infer = path_wrapper(infer_ass) -nodes.AssignAttr._infer = path_wrapper(infer_ass) + return bases._infer_stmts(stmts, context) +nodes.AssignName._infer = infer_ass +nodes.AssignAttr._infer = infer_ass # no infer method on DelName and DelAttr (expected InferenceError) - +@bases.path_wrapper def infer_empty_node(self, context=None): if not self.has_underlying_object(): - yield YES + yield util.YES else: try: for inferred in MANAGER.infer_ast_from_something(self.object, - context=context): + context=context): yield inferred - except AstroidError: - yield YES -nodes.EmptyNode._infer = path_wrapper(infer_empty_node) + except exceptions.AstroidError: + yield util.YES +nodes.EmptyNode._infer = infer_empty_node def infer_index(self, context=None): @@ -722,20 +642,19 @@ def instance_getitem(self, index, context=None): if context: new_context = context.clone() else: - context = new_context = InferenceContext() + context = new_context = contextmod.InferenceContext() # Create a new callcontext for providing index as an argument. - new_context.callcontext = CallContext( - args=[index], starargs=None, dstarargs=None) + new_context.callcontext = contextmod.CallContext(args=[index]) new_context.boundnode = self method = next(self.igetattr('__getitem__', context=context)) - if not isinstance(method, BoundMethod): - raise InferenceError + if not isinstance(method, bases.BoundMethod): + raise exceptions.InferenceError try: return next(method.infer_call_result(self, new_context)) except StopIteration: - raise InferenceError + raise exceptions.InferenceError -Instance.getitem = instance_getitem +bases.Instance.getitem = instance_getitem |