def _infer_context_manager(self, mgr, context): inferred = next(mgr.infer(context=context)) if isinstance(inferred, bases.Generator): # Check if it is decorated with contextlib.contextmanager. func = inferred.parent if not func.decorators: raise exceptions.InferenceError( "No decorators found on inferred generator %s", node=func ) for decorator_node in func.decorators.nodes: decorator = next(decorator_node.infer(context=context)) if isinstance(decorator, nodes.FunctionDef): if decorator.qname() == _CONTEXTLIB_MGR: break else: # It doesn't interest us. raise exceptions.InferenceError(node=func) # Get the first yield point. If it has multiple yields, # then a RuntimeError will be raised. possible_yield_points = func.nodes_of_class(nodes.Yield) # Ignore yields in nested functions yield_point = next( (node for node in possible_yield_points if node.scope() == func), None ) if yield_point: if not yield_point.value: const = nodes.Const(None) const.parent = yield_point const.lineno = yield_point.lineno yield const else: yield from yield_point.value.infer(context=context) elif isinstance(inferred, bases.Instance): try: enter = next(inferred.igetattr("__enter__", context=context)) except (exceptions.InferenceError, exceptions.AttributeInferenceError): raise exceptions.InferenceError(node=inferred) if not isinstance(enter, bases.BoundMethod): raise exceptions.InferenceError(node=enter) yield from enter.infer_call_result(self, context) else: raise exceptions.InferenceError(node=mgr)
def _infer_context_manager(self, mgr, context): try: inferred = next(mgr.infer(context=context)) except exceptions.InferenceError: return if isinstance(inferred, bases.Generator): # Check if it is decorated with contextlib.contextmanager. func = inferred.parent if not func.decorators: return for decorator_node in func.decorators.nodes: decorator = next(decorator_node.infer(context)) if isinstance(decorator, nodes.FunctionDef): if decorator.qname() == _CONTEXTLIB_MGR: break else: # It doesn't interest us. return # Get the first yield point. If it has multiple yields, # then a RuntimeError will be raised. # TODO(cpopa): Handle flows. yield_point = next(func.nodes_of_class(nodes.Yield), None) if yield_point: if not yield_point.value: # TODO(cpopa): an empty yield. Should be wrapped to Const. const = nodes.Const(None) const.parent = yield_point const.lineno = yield_point.lineno yield const else: for inferred in yield_point.value.infer(context=context): yield inferred elif isinstance(inferred, bases.Instance): try: enter = next(inferred.igetattr('__enter__', context=context)) except (exceptions.InferenceError, exceptions.AttributeInferenceError): return if not isinstance(enter, bases.BoundMethod): return if not context.callcontext: context.callcontext = contextmod.CallContext(args=[inferred]) for result in enter.infer_call_result(self, context): yield result
def infer_issubclass(callnode, context=None): """Infer issubclass() calls :param nodes.Call callnode: an `issubclass` call :param InferenceContext: the context for the inference :rtype nodes.Const: Boolean Const value of the `issubclass` call :raises UseInferenceDefault: If the node cannot be inferred """ call = arguments.CallSite.from_call(callnode, context=context) if call.keyword_arguments: # issubclass doesn't support keyword arguments raise UseInferenceDefault("TypeError: issubclass() takes no keyword arguments") if len(call.positional_arguments) != 2: raise UseInferenceDefault( "Expected two arguments, got {count}".format( count=len(call.positional_arguments) ) ) # The left hand argument is the obj to be checked obj_node, class_or_tuple_node = call.positional_arguments try: obj_type = next(obj_node.infer(context=context)) except InferenceError as exc: raise UseInferenceDefault from exc if not isinstance(obj_type, nodes.ClassDef): raise UseInferenceDefault("TypeError: arg 1 must be class") # The right hand argument is the class(es) that the given # object is to be checked against. try: class_container = _class_or_tuple_to_container( class_or_tuple_node, context=context ) except InferenceError as exc: raise UseInferenceDefault from exc try: issubclass_bool = helpers.object_issubclass(obj_type, class_container, context) except AstroidTypeError as exc: raise UseInferenceDefault("TypeError: " + str(exc)) from exc except MroError as exc: raise UseInferenceDefault from exc return nodes.Const(issubclass_bool)
def const_infer_binary_op(self, opnode, operator, other, context, _): not_implemented = nodes.Const(NotImplemented) if isinstance(other, nodes.Const): try: impl = BIN_OP_IMPL[operator] try: yield nodes.const_factory(impl(self.value, other.value)) except TypeError: # ArithmeticError is not enough: float >> float is a TypeError yield not_implemented except Exception: # pylint: disable=broad-except 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.Uninferable else: yield not_implemented
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 infer_len(node, context=None): """Infer length calls :param nodes.Call node: len call to infer :param context.InferenceContext: node context :rtype nodes.Const: a Const node with the inferred length, if possible """ call = arguments.CallSite.from_call(node, context=context) if call.keyword_arguments: raise UseInferenceDefault("TypeError: len() must take no keyword arguments") if len(call.positional_arguments) != 1: raise UseInferenceDefault( "TypeError: len() must take exactly one argument " "({len}) given".format(len=len(call.positional_arguments)) ) [argument_node] = call.positional_arguments try: return nodes.Const(helpers.object_len(argument_node, context=context)) except (AstroidTypeError, InferenceError) as exc: raise UseInferenceDefault(str(exc)) from exc
def infer_callable(node, context=None): """Understand callable calls This follows Python's semantics, where an object is callable if it provides an attribute __call__, even though that attribute is something which can't be called. """ if len(node.args) != 1: # Invalid callable call. raise UseInferenceDefault argument = node.args[0] try: inferred = next(argument.infer(context=context)) except InferenceError: return util.Uninferable if inferred is util.Uninferable: return util.Uninferable return nodes.Const(inferred.callable())
def visit_name(self, node, parent, assign_ctx=None): """visit a Name node by returning a fresh instance of it""" # True and False can be assigned to something in py2x, so we have to # check first the asscontext # pylint: disable=redefined-variable-type if assign_ctx == "Del": newnode = new.DelName() elif assign_ctx is not None: # Ass newnode = new.AssName() elif node.id in CONST_NAME_TRANSFORMS: newnode = new.Const(CONST_NAME_TRANSFORMS[node.id]) _set_infos(node, newnode, parent) return newnode else: newnode = new.Name() _lineno_parent(node, newnode, parent) newnode.name = node.id # XXX REMOVE me : if assign_ctx in ('Del', 'Assign'): # 'Aug' ?? self._save_assignment(newnode) return newnode
def visit_name(self, node, parent): """visit a Name node by returning a fresh instance of it""" # True and False can be assigned to something in py2x, so we have to # check first the asscontext if self.asscontext == "Del": newnode = new.DelName() elif self.asscontext is not None: # Ass assert self.asscontext == "Ass" newnode = new.AssName() elif node.id in CONST_NAME_TRANSFORMS: newnode = new.Const(CONST_NAME_TRANSFORMS[node.id]) _set_infos(node, newnode, parent) return newnode else: newnode = new.Name() _lineno_parent(node, newnode, parent) newnode.name = node.id # XXX REMOVE me : if self.asscontext in ('Del', 'Ass'): # 'Aug' ?? self._save_assignment(newnode) return newnode
def tl_infer_binary_op( self, opnode: nodes.BinOp, operator: str, other: nodes.NodeNG, context: InferenceContext, method: nodes.FunctionDef, ) -> Generator[nodes.NodeNG | type[util.Uninferable], None, None]: """Infer a binary operation on a tuple or list. The instance on which the binary operation is performed is a tuple or list. This refers to the left-hand side of the operation, so: 'tuple() + 1' or '[] + A()' """ # For tuples and list the boundnode is no longer the tuple or list instance context.boundnode = None not_implemented = nodes.Const(NotImplemented) if isinstance(other, self.__class__) and operator == "+": node = self.__class__(parent=opnode) node.elts = list( itertools.chain( _filter_uninferable_nodes(self.elts, context), _filter_uninferable_nodes(other.elts, context), )) yield node elif isinstance(other, nodes.Const) and operator == "*": if not isinstance(other.value, int): yield not_implemented return yield _multiply_seq_by_int(self, opnode, other, context) elif isinstance(other, bases.Instance) and operator == "*": # Verify if the instance supports __index__. as_index = helpers.class_instance_as_index(other) if not as_index: yield util.Uninferable else: yield _multiply_seq_by_int(self, opnode, as_index, context) else: yield not_implemented
def infer_isinstance(callnode, context=None): """Infer isinstance calls :param nodes.Call callnode: an isinstance call :param InferenceContext: context for call (currently unused but is a common interface for inference) :rtype nodes.Const: Boolean Const value of isinstance call :raises UseInferenceDefault: If the node cannot be inferred """ call = arguments.CallSite.from_call(callnode, context=context) if call.keyword_arguments: # isinstance doesn't support keyword arguments raise UseInferenceDefault("TypeError: isinstance() takes no keyword arguments") if len(call.positional_arguments) != 2: raise UseInferenceDefault( "Expected two arguments, got {count}".format( count=len(call.positional_arguments) ) ) # The left hand argument is the obj to be checked obj_node, class_or_tuple_node = call.positional_arguments # The right hand argument is the class(es) that the given # obj is to be check is an instance of try: class_container = _class_or_tuple_to_container( class_or_tuple_node, context=context ) except InferenceError as exc: raise UseInferenceDefault from exc try: isinstance_bool = helpers.object_isinstance(obj_node, class_container, context) except AstroidTypeError as exc: raise UseInferenceDefault("TypeError: " + str(exc)) from exc except MroError as exc: raise UseInferenceDefault from exc if isinstance_bool is util.Uninferable: raise UseInferenceDefault return nodes.Const(isinstance_bool)
def _resolve_asspart(parts, asspath, context): """recursive function to resolve multiple assignments""" asspath = asspath[:] index = asspath.pop(0) for part in parts: assigned = None if isinstance(part, nodes.Dict): # A dictionary in an iterating context try: assigned, _ = part.items[index] except IndexError: return elif hasattr(part, 'getitem'): index_node = nodes.Const(index) try: assigned = part.getitem(index_node, context) # XXX raise a specific exception to avoid potential hiding of # unexpected exception ? except (exceptions.AstroidTypeError, exceptions.AstroidIndexError): return if not assigned: return if not asspath: # we achieved to resolved the assignment path, don't infer the # last part yield assigned elif assigned is util.Uninferable: return else: # we are not yet on the last part of the path search on each # possibly inferred value try: yield from _resolve_asspart(assigned.infer(context), asspath, context) except exceptions.InferenceError: return
def tl_infer_binary_op(self, opnode, operator, other, context, method): not_implemented = nodes.Const(NotImplemented) if isinstance(other, self.__class__) and operator == '+': node = self.__class__(parent=opnode) elts = list(_filter_uninferable_nodes(self.elts, context)) elts += list(_filter_uninferable_nodes(other.elts, context)) node.elts = elts yield node elif isinstance(other, nodes.Const) and operator == '*': if not isinstance(other.value, int): yield not_implemented return yield _multiply_seq_by_int(self, opnode, other, context) elif isinstance(other, bases.Instance) and operator == '*': # Verify if the instance supports __index__. as_index = helpers.class_instance_as_index(other) if not as_index: yield util.Uninferable else: yield _multiply_seq_by_int(self, opnode, as_index, context) else: yield not_implemented
def visit_name(self, node, parent): """visit a Name node by returning a fresh instance of it""" context = self._get_context(node) # True and False can be assigned to something in py2x, so we have to # check first the context. if context == astroid.Del: newnode = nodes.DelName(node.id, node.lineno, node.col_offset, parent) elif context == astroid.Store: newnode = nodes.AssignName(node.id, node.lineno, node.col_offset, parent) elif node.id in CONST_NAME_TRANSFORMS: newnode = nodes.Const(CONST_NAME_TRANSFORMS[node.id], getattr(node, 'lineno', None), getattr(node, 'col_offset', None), parent) return newnode else: newnode = nodes.Name(node.id, node.lineno, node.col_offset, parent) # XXX REMOVE me : if context in (astroid.Del, astroid.Store): # 'Aug' ?? self._save_assignment(newnode) return newnode
def _resolve_looppart(parts, assign_path, context): """recursive function to resolve multiple assignments on loops""" assign_path = assign_path[:] index = assign_path.pop(0) for part in parts: if part is util.Uninferable: continue if not hasattr(part, "itered"): continue try: itered = part.itered() except TypeError: continue for stmt in itered: index_node = nodes.Const(index) try: assigned = stmt.getitem(index_node, context) except ( AttributeError, exceptions.AstroidTypeError, exceptions.AstroidIndexError, ): continue if not assign_path: # we achieved to resolved the assignment path, # don't infer the last part yield assigned elif assigned is util.Uninferable: break else: # we are not yet on the last part of the path # search on each possibly inferred value try: yield from _resolve_looppart( assigned.infer(context), assign_path, context ) except exceptions.InferenceError: break
def test_cls_special_attributes_1(self): cls = MODULE['YO'] self.assertEqual(len(cls.getattr('__bases__')), 1) self.assertEqual(len(cls.getattr('__name__')), 1) self.assertIsInstance(cls.getattr('__name__')[0], nodes.Const) self.assertEqual(cls.getattr('__name__')[0].value, 'YO') self.assertEqual(len(cls.getattr('__doc__')), 1) self.assertIsInstance(cls.getattr('__doc__')[0], nodes.Const) self.assertEqual(cls.getattr('__doc__')[0].value, 'hehe') self.assertEqual(len(cls.getattr('__module__')), 1) self.assertIsInstance(cls.getattr('__module__')[0], nodes.Const) 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__') for cls in (nodes.List._proxied, nodes.Const(1)._proxied): self.assertEqual(len(cls.getattr('__bases__')), 1) self.assertEqual(len(cls.getattr('__name__')), 1) self.assertEqual(len(cls.getattr('__doc__')), 1, (cls, cls.getattr('__doc__'))) self.assertEqual(cls.getattr('__doc__')[0].value, cls.doc) self.assertEqual(len(cls.getattr('__module__')), 1) self.assertEqual(len(cls.getattr('__dict__')), 1) self.assertEqual(len(cls.getattr('__mro__')), 1)
def _infer_compare(self: nodes.Compare, context: Optional[InferenceContext] = None) -> Any: """Chained comparison inference logic.""" retval = True ops = self.ops left_node = self.left lhs = list(left_node.infer(context=context)) # should we break early if first element is uninferable? for op, right_node in ops: # eagerly evaluate rhs so that values can be re-used as lhs rhs = list(right_node.infer(context=context)) try: retval = _do_compare(lhs, op, rhs) except AstroidTypeError: retval = util.Uninferable break if retval is not True: break # short-circuit lhs = rhs # continue if retval is util.Uninferable: yield retval else: yield nodes.Const(retval)
def test_copy(self): """ Make sure copying a Const object doesn't result in infinite recursion """ const = copy.copy(nodes.Const(1)) assert const.value == 1
def visit_nameconstant(self, node, parent): # in Python 3.4 we have NameConstant for True / False / None return nodes.Const(node.value, getattr(node, 'lineno', None), getattr(node, 'col_offset', None), parent)
def visit_num(self, node, parent): """visit a Num node by returning a fresh instance of Const""" return nodes.Const(node.n, getattr(node, 'lineno', None), getattr(node, 'col_offset', None), parent)
def visit_str(self, node, parent): """visit a String/Bytes node by returning a fresh instance of Const""" return nodes.Const(node.s, getattr(node, 'lineno', None), getattr(node, 'col_offset', None), parent)
def visit_constant(self, node, parent): """visit a Constant node by returning a fresh instance of Const""" return nodes.Const(node.value, getattr(node, 'lineno', None), getattr(node, 'col_offset', None), parent)
def infer_dict_fromkeys(node, context=None): """Infer dict.fromkeys :param nodes.Call node: dict.fromkeys() call to infer :param context.InferenceContext: node context :rtype nodes.Dict: a Dictionary containing the values that astroid was able to infer. In case the inference failed for any reason, an empty dictionary will be inferred instead. """ def _build_dict_with_elements(elements): new_node = nodes.Dict(col_offset=node.col_offset, lineno=node.lineno, parent=node.parent) new_node.postinit(elements) return new_node call = arguments.CallSite.from_call(node, context=context) if call.keyword_arguments: raise UseInferenceDefault( "TypeError: int() must take no keyword arguments") if len(call.positional_arguments) not in {1, 2}: raise UseInferenceDefault( "TypeError: Needs between 1 and 2 positional arguments") default = nodes.Const(None) values = call.positional_arguments[0] try: inferred_values = next(values.infer(context=context)) except InferenceError: return _build_dict_with_elements([]) if inferred_values is util.Uninferable: return _build_dict_with_elements([]) # Limit to a couple of potential values, as this can become pretty complicated accepted_iterable_elements = (nodes.Const, ) if isinstance(inferred_values, (nodes.List, nodes.Set, nodes.Tuple)): elements = inferred_values.elts for element in elements: if not isinstance(element, accepted_iterable_elements): # Fallback to an empty dict return _build_dict_with_elements([]) elements_with_value = [(element, default) for element in elements] return _build_dict_with_elements(elements_with_value) elif isinstance(inferred_values, nodes.Const) and isinstance( inferred_values.value, (str, bytes)): elements = [(nodes.Const(element), default) for element in inferred_values.value] return _build_dict_with_elements(elements) elif isinstance(inferred_values, nodes.Dict): keys = inferred_values.itered() for key in keys: if not isinstance(key, accepted_iterable_elements): # Fallback to an empty dict return _build_dict_with_elements([]) elements_with_value = [(element, default) for element in keys] return _build_dict_with_elements(elements_with_value) # Fallback to an empty dictionary return _build_dict_with_elements([])
def test_unpacking_in_dict_getitem(self): node = extract_node('{1:2, **{2:3, 3:4}, **{5: 6}}') for key, expected in ((1, 2), (2, 3), (3, 4), (5, 6)): value = node.getitem(nodes.Const(key)) self.assertIsInstance(value, nodes.Const) self.assertEqual(value.value, expected)
def test_genexpr(node): if node.elt.value == 1: node.elt = nodes.Const(2, node.lineno, node.col_offset, node.parent) return node return None
def visit_num(self, node, parent): """visit a Num node by returning a fresh instance of Const""" newnode = new.Const(node.n) _set_infos(node, newnode, parent) return newnode
def visit_nameconstant(self, node, parent): # in Python 3.4 we have NameConstant for True / False / None newnode = new.Const(node.value) _set_infos(node, newnode, parent) return newnode
def _patch_uuid_class(node): # The .int member is patched using __dict__ node.locals["int"] = [nodes.Const(0, parent=node)]
def visit_str(self, node, parent): """visit a Str node by returning a fresh instance of Const""" newnode = new.Const(node.s) _set_infos(node, newnode, parent) return newnode
def _check_new_format_specifiers(self, node, fields, named): """ Check attribute and index access in the format string ("{0.a}" and "{0[a]}"). """ for key, specifiers in fields: # Obtain the argument. If it can't be obtained # or inferred, skip this check. if key == "": # {[0]} will have an unnamed argument, defaulting # to 0. It will not be present in `named`, so use the value # 0 for it. key = 0 if isinstance(key, numbers.Number): try: argname = utils.get_argument_from_call(node, key) except utils.NoSuchArgumentError: continue else: if key not in named: continue argname = named[key] if argname in (astroid.Uninferable, None): continue try: argument = utils.safe_infer(argname) except astroid.InferenceError: continue if not specifiers or not argument: # No need to check this key if it doesn't # use attribute / item access continue if argument.parent and isinstance(argument.parent, nodes.Arguments): # Ignore any object coming from an argument, # because we can't infer its value properly. continue previous = argument parsed = [] for is_attribute, specifier in specifiers: if previous is astroid.Uninferable: break parsed.append((is_attribute, specifier)) if is_attribute: try: previous = previous.getattr(specifier)[0] except astroid.NotFoundError: if (hasattr(previous, "has_dynamic_getattr") and previous.has_dynamic_getattr()): # Don't warn if the object has a custom __getattr__ break path = get_access_path(key, parsed) self.add_message( "missing-format-attribute", args=(specifier, path), node=node, ) break else: warn_error = False if hasattr(previous, "getitem"): try: previous = previous.getitem(nodes.Const(specifier)) except ( astroid.AstroidIndexError, astroid.AstroidTypeError, astroid.AttributeInferenceError, ): warn_error = True except astroid.InferenceError: break if previous is astroid.Uninferable: break else: try: # Lookup __getitem__ in the current node, # but skip further checks, because we can't # retrieve the looked object previous.getattr("__getitem__") break except astroid.NotFoundError: warn_error = True if warn_error: path = get_access_path(key, parsed) self.add_message("invalid-format-index", args=(specifier, path), node=node) break try: previous = next(previous.infer()) except astroid.InferenceError: # can't check further if we can't infer it break