def _infer_stmts(stmts, context, frame=None): """Return an iterator on statements inferred by each statement in *stmts*.""" inferred = False if context is not None: name = context.lookupname context = context.clone() else: name = None context = contextmod.InferenceContext() for stmt in stmts: if stmt is util.Uninferable: yield stmt inferred = True continue context.lookupname = stmt._infer_name(frame, name) try: for inferred in stmt.infer(context=context): yield inferred inferred = True except exceptions.NameInferenceError: continue except exceptions.InferenceError: yield util.Uninferable inferred = True if not inferred: raise exceptions.InferenceError( "Inference failed for all members of {stmts!r}.", stmts=stmts, frame=frame, context=context, )
def _infer_augassign(self, context=None): """Inference logic for augmented binary operations.""" if context is None: context = contextmod.InferenceContext() for lhs in self.target.infer_lhs(context=context): if lhs is util.Uninferable: # Don't know how to process this. yield util.Uninferable return # TODO(cpopa): if we have A() * A(), trying to infer # the rhs with the same context will result in an # inference error, so we create another context for it. # This is a bug which should be fixed in InferenceContext at some point. rhs_context = context.clone() rhs_context.path = set() for rhs in self.value.infer(context=rhs_context): if rhs is util.Uninferable: # Don't know how to process this. yield util.Uninferable return try: results = _infer_binary_operation(lhs, rhs, self, context, _get_aug_flow) except exceptions._NonDeducibleTypeHierarchy: yield util.Uninferable else: for result in results: yield result
def instance_getitem(self, index, context=None): # Rewrap index to Const for this case if context: new_context = context.clone() else: context = new_context = contextmod.InferenceContext() # Create a new callcontext for providing index as an argument. new_context.callcontext = contextmod.CallContext(args=[index]) new_context.boundnode = self method = next(self.igetattr('__getitem__', context=context)) if not isinstance(method, bases.BoundMethod): 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( message='Inference for {node!r}[{index!s}] failed.', node=self, index=index, context=context))
def _infer_binop(self, context): """Binary operation inferrence logic.""" if context is None: context = contextmod.InferenceContext() left = self.left right = self.right # we use two separate contexts for evaluating lhs and rhs because # 1. evaluating lhs may leave some undesired entries in context.path # which may not let us infer right value of rhs lhs_context = context.clone() rhs_context = context.clone() for lhs in left.infer(context=lhs_context): if lhs is util.Uninferable: # Don't know how to process this. yield util.Uninferable return for rhs in right.infer(context=rhs_context): if rhs is util.Uninferable: # Don't know how to process this. yield util.Uninferable return try: results = _infer_binary_operation(lhs, rhs, self, context, _get_binop_flow) except exceptions._NonDeducibleTypeHierarchy: yield util.Uninferable else: for result in results: yield result
def igetattr(self, name, context=None): """inferred getattr""" if not context: context = contextmod.InferenceContext() try: # avoid recursively inferring the same attr on the same class if context.push((self._proxied, name)): return # XXX frame should be self._proxied, or not ? get_attr = self.getattr(name, context, lookupclass=False) for stmt in _infer_stmts(self._wrap_attr(get_attr, context), context, frame=self): yield stmt except exceptions.AttributeInferenceError: try: # fallback to class.igetattr since it has some logic to handle # descriptors attrs = self._proxied.igetattr(name, context, class_context=False) for stmt in self._wrap_attr(attrs, context): yield stmt except exceptions.AttributeInferenceError as error: util.reraise(exceptions.InferenceError(**vars(error)))
def _query_augassign(self, context=None): """query logic for augmented binary operations.""" if context is None: context = contextmod.InferenceContext() rhs_context = context.clone() lhs_res = self.target.query_lhs(context=context) rhs_res = self.value.query(context=rhs_context) res = [] for lhs, rhs in itertools.product(lhs_res, rhs_res): if any(value is util.Unqueryable for value in (rhs, lhs)): # Don't know how to process this. continue # return util.Unqueryable try: result = _query_binary_operation( left=lhs, right=rhs, binary_opnode=self, context=context, # flow_factory=_get_aug_flow, ) res.extend(result) except exceptions._NonDeducibleTypeHierarchy: continue return res
def igetattr(self, name, context=None): """inferred getattr""" if not context: context = contextmod.InferenceContext() try: # avoid recursively inferring the same attr on the same class if context.push((self._proxied, name)): return # XXX frame should be self._proxied, or not ? get_attr = self.getattr(name, context, lookupclass=False) yield from _infer_stmts(self._wrap_attr(get_attr, context), context, frame=self) except exceptions.AttributeInferenceError as error: try: # fallback to class.igetattr since it has some logic to handle # descriptors # But only if the _proxied is the Class. if self._proxied.__class__.__name__ != "ClassDef": raise exceptions.InferenceError(**vars(error)) from error attrs = self._proxied.igetattr(name, context, class_context=False) yield from self._wrap_attr(attrs, context) except exceptions.AttributeInferenceError as error: raise exceptions.InferenceError(**vars(error)) from error
def bool_value(self): """Infer the truth value for an Instance The truth value of an instance is determined by these conditions: * if it implements __bool__ on Python 3 or __nonzero__ on Python 2, then its bool value will be determined by calling this special method and checking its result. * when this method is not defined, __len__() is called, if it is defined, and the object is considered true if its result is nonzero. If a class defines neither __len__() nor __bool__(), all its instances are considered true. """ context = contextmod.InferenceContext() context.callcontext = contextmod.CallContext(args=[]) context.boundnode = self try: result = _infer_method_result_truth(self, BOOL_SPECIAL_METHOD, context) except (exceptions.InferenceError, exceptions.AttributeInferenceError): # Fallback to __len__. try: result = _infer_method_result_truth(self, "__len__", context) except (exceptions.AttributeInferenceError, exceptions.InferenceError): return True return result
def wrapped(node, context=None, _func=func, **kwargs): """wrapper function handling context""" if context is None: context = contextmod.InferenceContext() if context.push(node): return None yielded = set() generator = _func(node, context, **kwargs) while True: try: res = next(generator) except StopIteration as error: if error.args: return error.args[0] return None # 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)
def _infer_augassign(self, context=None): """Inference logic for augmented binary operations.""" if context is None: context = contextmod.InferenceContext() rhs_context = context.clone() lhs_iter = self.target.infer_lhs(context=context) rhs_iter = self.value.infer(context=rhs_context) for lhs, rhs in itertools.product(lhs_iter, rhs_iter): if any(value is util.Uninferable for value in (rhs, lhs)): # Don't know how to process this. yield util.Uninferable return try: yield from _infer_binary_operation( left=lhs, right=rhs, binary_opnode=self, context=context, flow_factory=_get_aug_flow, ) except exceptions._NonDeducibleTypeHierarchy: yield util.Uninferable
def wrapped(node, context=None, _func=func, **kwargs): """wrapper function handling context""" if context is None: context = contextmod.InferenceContext() if context.push(node): return None yielded = set() 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 error.args: return error.args[0] return None
def infer_ifexp(self, context=None): """Support IfExp inference If we can't infer the truthiness of the condition, we default to inferring both branches. Otherwise, we infer either branch depending on the condition. """ both_branches = False # We use two separate contexts for evaluating lhs and rhs because # evaluating lhs may leave some undesired entries in context.path # which may not let us infer right value of rhs. context = context or contextmod.InferenceContext() lhs_context = contextmod.copy_context(context) rhs_context = contextmod.copy_context(context) try: test = next(self.test.infer(context=context.clone())) except exceptions.InferenceError: both_branches = True else: if test is not util.Uninferable: if test.bool_value(): yield from self.body.infer(context=lhs_context) else: yield from self.orelse.infer(context=rhs_context) else: both_branches = True if both_branches: yield from self.body.infer(context=lhs_context) yield from self.orelse.infer(context=rhs_context)
def ilookup(self, name): """inferred lookup return an iterator on inferred values of the statements returned by the lookup method """ frame, stmts = self.lookup(name) context = contextmod.InferenceContext() return bases._infer_stmts(stmts, context, frame)
def test_absolute_import(self): module = resources.build_file('data/absimport.py') ctx = contextmod.InferenceContext() # will fail if absolute import failed ctx.lookupname = 'message' next(module['message'].infer(ctx)) ctx.lookupname = 'email' m = next(module['email'].infer(ctx)) self.assertFalse(m.file.startswith(os.path.join('data', 'email.py')))
def test_absolute_import(self): module = resources.build_file("data/absimport.py") ctx = contextmod.InferenceContext() # will fail if absolute import failed ctx.lookupname = "message" next(module["message"].infer(ctx)) ctx.lookupname = "email" m = next(module["email"].infer(ctx)) self.assertFalse(m.file.startswith(os.path.join("data", "email.py")))
def from_call(cls, call_node, context=None): """Get a CallSite object from the given Call node. :param context: An instance of :class:`astroid.context.Context` that will be used to force a single inference path. """ # Determine the callcontext from the given `context` object if any. context = context or contextmod.InferenceContext() callcontext = contextmod.CallContext(call_node.args, call_node.keywords) return cls(callcontext, context=context)
def starred_assigned_stmts(self, node=None, context=None, asspath=None): stmt = self.statement() if not isinstance(stmt, (nodes.Assign, nodes.For)): raise exceptions.InferenceError() 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() if context is None: context = contextmod.InferenceContext() try: rhs = next(value.infer(context)) except exceptions.InferenceError: yield util.YES return if rhs is util.YES or not hasattr(rhs, 'elts'): # Not interested in inferred values without elts. yield util.YES return elts = collections.deque(rhs.elts[:]) if len(lhs.elts) > len(rhs.elts): # a, *b, c = (1, 2) raise exceptions.InferenceError() # Unpack iteratively the values from the rhs of the assignment, # until the find the starred node. What will remain will # be the list of values which the Starred node will represent # This is done in two steps, from left to right to remove # anything before the starred node and from right to left # to remvoe anything after the starred node. for index, node in enumerate(lhs.elts): if not isinstance(node, nodes.Starred): elts.popleft() continue lhs_elts = collections.deque(reversed(lhs.elts[index:])) for node in lhs_elts: if not isinstance(node, nodes.Starred): elts.pop() continue # We're done packed = nodes.List() packed.elts = elts packed.parent = self yield packed break
def wrapped(node, context=None, _func=func, **kwargs): """wrapper function handling context""" if context is None: context = contextmod.InferenceContext() if context.push(node): return None try: return _func(node, context, **kwargs) except StopIteration as error: if error.args: return error.args[0] return None
def wrapped(node, context=None, _func=func, **kwargs): """wrapper function handling context""" if context is None: context = contextmod.InferenceContext() context.push(node) yielded = set() for res in _func(node, context, **kwargs): # unproxy only true instance, not const, tuple, dict... if res.__class__ is Instance: ares = res._proxied else: ares = res if ares not in yielded: yield res yielded.add(ares)
def query_attribute(self, context=None): """query an Attribute node by using getattr on the associated object""" res = [] for owner in self.expr.query(context): if owner is util.Uninferable: assert False if owner is util.Unqueryable: continue if owner.query_end: res.extend([owner, util.Unqueryable]) continue if context and context.boundnode: # This handles the situation where the attribute is accessed through a subclass # of a base class and the attribute is defined at the base class's level, # by taking in consideration a redefinition in the subclass. if isinstance(owner, bases.Instance) and isinstance( context.boundnode, bases.Instance): try: if helpers.is_subtype( helpers.object_type(context.boundnode), helpers.object_type(owner), ): owner = context.boundnode except exceptions._NonDeducibleTypeHierarchy: # Can't determine anything useful. pass elif not context: context = contextmod.InferenceContext() try: context.boundnode = owner res.extend(owner.query_attr(self.attrname, context)) except ( exceptions.AttributeInferenceError, exceptions.InferenceError, AttributeError, ): import traceback traceback.print_exc() pass finally: context.boundnode = None if len(res) == 0: return [util.Unqueryable] return res
def _object_type(node, context=None): astroid_manager = manager.AstroidManager() builtins = astroid_manager.builtins_module context = context or contextmod.InferenceContext() for inferred in node.infer(context=context): if isinstance(inferred, scoped_nodes.ClassDef): if inferred.newstyle: metaclass = inferred.metaclass(context=context) if metaclass: yield metaclass continue yield builtins.getattr("type")[0] elif isinstance(inferred, (scoped_nodes.Lambda, bases.UnboundMethod)): yield _function_type(inferred, builtins) elif isinstance(inferred, scoped_nodes.Module): yield _build_proxy_class("module", builtins) else: yield inferred._proxied
def infer_call_result(self, caller, context=None): if context is None: context = contextmod.InferenceContext() context = context.clone() context.boundnode = self.bound if (self.bound.__class__.__name__ == 'ClassDef' and self.bound.name == 'type' and self.name == '__new__' and len(caller.args) == 4 # TODO(cpopa): this check shouldn't be needed. and self._proxied.parent.frame().qname() == '%s.object' % BUILTINS): # Check if we have an ``type.__new__(mcs, name, bases, attrs)`` call. new_cls = self._infer_type_new_call(caller, context) if new_cls: return iter((new_cls, )) return super(BoundMethod, self).infer_call_result(caller, context)
def _unpack_keywords(self, keywords, context=None): values = {} context = context or contextmod.InferenceContext() context.extra_context = self.argument_context_map for name, value in keywords: if name is None: # Then it's an unpacking operation (**) try: inferred = next(value.infer(context=context)) except InferenceError: values[name] = util.Uninferable continue except StopIteration: continue if not isinstance(inferred, nodes.Dict): # Not something we can work with. values[name] = util.Uninferable continue for dict_key, dict_value in inferred.items: try: dict_key = next(dict_key.infer(context=context)) except InferenceError: values[name] = util.Uninferable continue except StopIteration: continue if not isinstance(dict_key, nodes.Const): values[name] = util.Uninferable continue if not isinstance(dict_key.value, str): values[name] = util.Uninferable continue if dict_key.value in values: # The name is already in the dictionary values[dict_key.value] = util.Uninferable self.duplicated_keywords.add(dict_key.value) continue values[dict_key.value] = dict_value else: values[name] = value return values
def instance_getitem(self, index, context=None): # Rewrap index to Const for this case index = nodes.Const(index) if context: new_context = context.clone() else: context = new_context = contextmod.InferenceContext() # Create a new callcontext for providing index as an argument. new_context.callcontext = contextmod.CallContext(args=[index]) new_context.boundnode = self method = next(self.igetattr('__getitem__', context=context)) if not isinstance(method, bases.BoundMethod): raise exceptions.InferenceError try: return next(method.infer_call_result(self, new_context)) except StopIteration: raise exceptions.InferenceError
def _unpack_args(args): values = [] context = contextmod.InferenceContext() for arg in args: if isinstance(arg, nodes.Starred): try: inferred = next(arg.value.infer(context=context)) except exceptions.InferenceError: values.append(util.Uninferable) continue if inferred is util.Uninferable: values.append(util.Uninferable) continue if not hasattr(inferred, 'elts'): values.append(util.Uninferable) continue values.extend(inferred.elts) else: values.append(arg) return values
def class_instance_as_index(node): """Get the value as an index for the given instance. If an instance provides an __index__ method, then it can be used in some scenarios where an integer is expected, for instance when multiplying or subscripting a list. """ context = contextmod.InferenceContext() context.callcontext = contextmod.CallContext(args=[node]) try: for inferred in node.igetattr("__index__", context=context): if not isinstance(inferred, bases.BoundMethod): continue for result in inferred.infer_call_result(node, context=context): if isinstance(result, nodes.Const) and isinstance(result.value, int): return result except exceptions.InferenceError: pass return None
def _unpack_args(self, args, context=None): values = [] context = context or contextmod.InferenceContext() context.extra_context = self.argument_context_map for arg in args: if isinstance(arg, nodes.Starred): try: inferred = next(arg.value.infer(context=context)) except exceptions.InferenceError: values.append(util.Uninferable) continue if inferred is util.Uninferable: values.append(util.Uninferable) continue if not hasattr(inferred, "elts"): values.append(util.Uninferable) continue values.extend(inferred.elts) else: values.append(arg) return values
def igetattr(self, name, context=None): """inferred getattr""" if not context: context = contextmod.InferenceContext() try: # avoid recursively inferring the same attr on the same class context.push((self._proxied, name)) # XXX frame should be self._proxied, or not ? get_attr = self.getattr(name, context, lookupclass=False) return _infer_stmts( self._wrap_attr(get_attr, context), context, frame=self, ) except exceptions.NotFoundError: try: # fallback to class'igetattr since it has some logic to handle # descriptors return self._wrap_attr(self._proxied.igetattr(name, context), context) except exceptions.NotFoundError: raise exceptions.InferenceError(name)
def _infer_augassign(self, context=None): """Inference logic for augmented binary operations.""" if context is None: context = contextmod.InferenceContext() for lhs in self.target.infer_lhs(context=context): if lhs is util.Uninferable: # Don't know how to process this. yield util.Uninferable return rhs_context = context.clone() for rhs in self.value.infer(context=rhs_context): if rhs is util.Uninferable: # Don't know how to process this. yield util.Uninferable return try: yield from _infer_binary_operation(lhs, rhs, self, context, _get_aug_flow) except exceptions._NonDeducibleTypeHierarchy: yield util.Uninferable
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.Uninferable: yield owner continue if context and context.boundnode: # This handles the situation where the attribute is accessed through a subclass # of a base class and the attribute is defined at the base class's level, # by taking in consideration a redefinition in the subclass. if isinstance(owner, bases.Instance) and isinstance( context.boundnode, bases.Instance ): try: if helpers.is_subtype( helpers.object_type(context.boundnode), helpers.object_type(owner), ): owner = context.boundnode except _NonDeducibleTypeHierarchy: # Can't determine anything useful. pass elif not context: context = contextmod.InferenceContext() old_boundnode = context.boundnode try: context.boundnode = owner yield from owner.igetattr(self.attrname, context) except ( AttributeInferenceError, InferenceError, AttributeError, ): pass finally: context.boundnode = old_boundnode return dict(node=self, context=context)