class Generator(BaseInstance): """a special node representing a generator. Proxied class is set once for all in raw_building. """ # pylint: disable=unnecessary-lambda special_attributes = util.lazy_descriptor( lambda: objectmodel.GeneratorModel()) # pylint: disable=super-init-not-called def __init__(self, parent=None): self.parent = parent def callable(self): return False def pytype(self): return '%s.generator' % BUILTINS def display_type(self): return 'Generator' def bool_value(self): return True def __repr__(self): return '<Generator(%s) l.%s at 0x%s>' % (self._proxied.name, self.lineno, id(self)) def __str__(self): return 'Generator(%s)' % (self._proxied.name)
class Generator(BaseInstance): """a special node representing a generator. Proxied class is set once for all in raw_building. """ special_attributes = lazy_descriptor(objectmodel.GeneratorModel) def __init__(self, parent=None, generator_initial_context=None): super().__init__() self.parent = parent self._call_context = copy_context(generator_initial_context) @decorators.cached def infer_yield_types(self): yield from self.parent.infer_yield_result(self._call_context) def callable(self): return False def pytype(self): return "builtins.generator" def display_type(self): return "Generator" def bool_value(self, context=None): return True def __repr__(self): return f"<Generator({self._proxied.name}) l.{self.lineno} at 0x{id(self)}>" def __str__(self): return f"Generator({self._proxied.name})"
class Property(scoped_nodes.FunctionDef): """Class representing a Python property""" def __init__(self, function, name=None, doc=None, lineno=None, col_offset=None, parent=None): self.function = function super().__init__(name, doc, lineno, col_offset, parent) # pylint: disable=unnecessary-lambda special_attributes = util.lazy_descriptor( lambda: objectmodel.PropertyModel()) type = "property" def pytype(self): return "%s.property" % BUILTINS def infer_call_result(self, caller=None, context=None): raise exceptions.InferenceError("Properties are not callable") def infer(self, context=None, **kwargs): return iter((self, ))
class Instance(BaseInstance): """A special node representing a class instance.""" # pylint: disable=unnecessary-lambda special_attributes = util.lazy_descriptor( lambda: objectmodel.InstanceModel()) def __repr__(self): return '<Instance of %s.%s at 0x%s>' % (self._proxied.root().name, self._proxied.name, id(self)) def __str__(self): return 'Instance of %s.%s' % (self._proxied.root().name, self._proxied.name) def callable(self): try: self._proxied.getattr('__call__', class_context=False) return True except exceptions.AttributeInferenceError: return False def pytype(self): return self._proxied.qname() def display_type(self): return 'Instance of' 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 # This is set in inference.py. def getitem(self, index, context=None): pass
class Property(scoped_nodes.FunctionDef): """Class representing a Python property""" @decorators.deprecate_arguments( doc="Use the postinit arg 'doc_node' instead") def __init__(self, function, name=None, doc=None, lineno=None, col_offset=None, parent=None): self.function = function super().__init__(name, lineno=lineno, col_offset=col_offset, parent=parent) # Assigned directly to prevent triggering the DeprecationWarning. self._doc = doc # pylint: disable=unnecessary-lambda special_attributes = util.lazy_descriptor( lambda: objectmodel.PropertyModel()) type = "property" def pytype(self) -> Literal["builtins.property"]: return "builtins.property" def infer_call_result(self, caller=None, context=None): raise InferenceError("Properties are not callable") def _infer(self: _T, context: InferenceContext | None = None, **kwargs: Any) -> Generator[_T, None, None]: yield self
class UnboundMethod(Proxy): """a special node representing a method not bound to an instance""" # pylint: disable=unnecessary-lambda special_attributes = util.lazy_descriptor( lambda: objectmodel.UnboundMethodModel()) def __repr__(self): frame = self._proxied.parent.frame() return "<%s %s of %s at 0x%s" % ( self.__class__.__name__, self._proxied.name, frame.qname(), id(self), ) def implicit_parameters(self): return 0 def is_bound(self): return False def getattr(self, name, context=None): if name in self.special_attributes: return [self.special_attributes.lookup(name)] return self._proxied.getattr(name, context) def igetattr(self, name, context=None): if name in self.special_attributes: return iter((self.special_attributes.lookup(name), )) return self._proxied.igetattr(name, context) def infer_call_result(self, caller, context): """ The boundnode of the regular context with a function called on ``object.__new__`` will be of type ``object``, which is incorrect for the argument in general. If no context is given the ``object.__new__`` call argument will correctly inferred except when inside a call that requires the additional context (such as a classmethod) of the boundnode to determine which class the method was called from """ # If we're unbound method __new__ of builtin object, the result is an # instance of the class given as first argument. if (self._proxied.name == "__new__" and self._proxied.parent.frame().qname() == "%s.object" % BUILTINS): if caller.args: node_context = context.extra_context.get(caller.args[0]) infer = caller.args[0].infer(context=node_context) else: infer = [] return (Instance(x) if x is not util.Uninferable else x for x in infer) return self._proxied.infer_call_result(caller, context) def bool_value(self): return True
class DictInstance(bases.Instance): """Special kind of instances for dictionaries This instance knows the underlying object model of the dictionaries, which means that methods such as .values or .items can be properly inferred. """ special_attributes = util.lazy_descriptor(lambda: objectmodel.DictModel())
class ExceptionInstance(bases.Instance): """Class for instances of exceptions It has special treatment for some of the exceptions's attributes, which are transformed at runtime into certain concrete objects, such as the case of .args. """ special_attributes = util.lazy_descriptor( lambda: objectmodel.ExceptionInstanceModel())
class UnboundMethod(Proxy): """a special node representing a method not bound to an instance""" # pylint: disable=unnecessary-lambda special_attributes = util.lazy_descriptor( lambda: objectmodel.UnboundMethodModel()) def __repr__(self): frame = self._proxied.parent.frame() return '<%s %s of %s at 0x%s' % (self.__class__.__name__, self._proxied.name, frame.qname(), id(self)) def is_bound(self): return False def getattr(self, name, context=None): if name in self.special_attributes: return [self.special_attributes.lookup(name)] return self._proxied.getattr(name, context) def igetattr(self, name, context=None): if name in self.special_attributes: return iter((self.special_attributes.lookup(name), )) return self._proxied.igetattr(name, context) def infer_call_result(self, caller, context): # If we're unbound method __new__ of builtin object, the result is an # instance of the class given as first argument. if (self._proxied.name == '__new__' and self._proxied.parent.frame().qname() == '%s.object' % BUILTINS): infer = caller.args[0].infer() if caller.args else [] return (Instance(x) if x is not util.Uninferable else x for x in infer) return self._proxied.infer_call_result(caller, context) def bool_value(self): return True
class Super(node_classes.NodeNG): """Proxy class over a super call. This class offers almost the same behaviour as Python's super, which is MRO lookups for retrieving attributes from the parents. The *mro_pointer* is the place in the MRO from where we should start looking, not counting it. *mro_type* is the object which provides the MRO, it can be both a type or an instance. *self_class* is the class where the super call is, while *scope* is the function where the super call is. """ # pylint: disable=unnecessary-lambda special_attributes = util.lazy_descriptor(lambda: objectmodel.SuperModel()) # pylint: disable=super-init-not-called def __init__(self, mro_pointer, mro_type, self_class, scope): self.type = mro_type self.mro_pointer = mro_pointer self._class_based = False self._self_class = self_class self._scope = scope def _infer(self, context=None): yield self 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.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. self._class_based = True mro_type = self.type else: mro_type = getattr(self.type, "_proxied", None) if not isinstance(mro_type, (bases.Instance, scoped_nodes.ClassDef)): 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.", super_=self) mro = mro_type.mro() if self.mro_pointer not in mro: 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:] @decorators.cachedproperty def _proxied(self): ast_builtins = MANAGER.builtins_module return ast_builtins.getattr("super")[0] def pytype(self): return "%s.super" % BUILTINS def display_type(self): return "Super of" @property def name(self): """Get the name of the MRO pointer.""" return self.mro_pointer.name def qname(self): return "super" def igetattr(self, name, context=None): """Retrieve the inferred values of the given attribute name.""" if name in self.special_attributes: yield self.special_attributes.lookup(name) return try: mro = self.super_mro() # Don't let invalid MROs or invalid super calls # leak out as is from this function. except exceptions.SuperError as exc: raise exceptions.AttributeInferenceError( ("Lookup for {name} on {target!r} because super call {super!r} " "is invalid."), target=self, attribute=name, context=context, super_=exc.super_, ) from exc except exceptions.MroError as exc: raise 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, ) from exc found = False for cls in mro: if name not in cls.locals: continue found = True for inferred in bases._infer_stmts([cls[name]], context, frame=self): if not isinstance(inferred, scoped_nodes.FunctionDef): yield inferred continue # We can obtain different descriptors from a super depending # on what we are accessing and where the super call is. if inferred.type == "classmethod": yield bases.BoundMethod(inferred, cls) elif self._scope.type == "classmethod" and inferred.type == "method": yield inferred elif self._class_based or inferred.type == "staticmethod": yield inferred elif bases._is_property(inferred): # TODO: support other descriptors as well. try: yield from inferred.infer_call_result(self, context) except exceptions.InferenceError: yield util.Uninferable else: yield bases.BoundMethod(inferred, cls) if not found: raise exceptions.AttributeInferenceError(target=self, attribute=name, context=context) def getattr(self, name, context=None): return list(self.igetattr(name, context=context))
class BoundMethod(UnboundMethod): """a special node representing a method bound to an instance""" # pylint: disable=unnecessary-lambda special_attributes = lazy_descriptor( lambda: objectmodel.BoundMethodModel()) def __init__(self, proxy, bound): super().__init__(proxy) self.bound = bound def implicit_parameters(self): if self.name == "__new__": # __new__ acts as a classmethod but the class argument is not implicit. return 0 return 1 def is_bound(self): return True def _infer_type_new_call(self, caller, context): """Try to infer what type.__new__(mcs, name, bases, attrs) returns. In order for such call to be valid, the metaclass needs to be a subtype of ``type``, the name needs to be a string, the bases needs to be a tuple of classes """ # pylint: disable=import-outside-toplevel; circular import from astroid.nodes import Pass # Verify the metaclass try: mcs = next(caller.args[0].infer(context=context)) except StopIteration as e: raise InferenceError(context=context) from e if mcs.__class__.__name__ != "ClassDef": # Not a valid first argument. return None if not mcs.is_subtype_of("builtins.type"): # Not a valid metaclass. return None # Verify the name try: name = next(caller.args[1].infer(context=context)) except StopIteration as e: raise InferenceError(context=context) from e if name.__class__.__name__ != "Const": # Not a valid name, needs to be a const. return None if not isinstance(name.value, str): # Needs to be a string. return None # Verify the bases try: bases = next(caller.args[2].infer(context=context)) except StopIteration as e: raise InferenceError(context=context) from e if bases.__class__.__name__ != "Tuple": # Needs to be a tuple. return None try: inferred_bases = [ next(elt.infer(context=context)) for elt in bases.elts ] except StopIteration as e: raise InferenceError(context=context) from e if any(base.__class__.__name__ != "ClassDef" for base in inferred_bases): # All the bases needs to be Classes return None # Verify the attributes. try: attrs = next(caller.args[3].infer(context=context)) except StopIteration as e: raise InferenceError(context=context) from e if attrs.__class__.__name__ != "Dict": # Needs to be a dictionary. return None cls_locals = collections.defaultdict(list) for key, value in attrs.items: try: key = next(key.infer(context=context)) except StopIteration as e: raise InferenceError(context=context) from e try: value = next(value.infer(context=context)) except StopIteration as e: raise InferenceError(context=context) from e # Ignore non string keys if key.__class__.__name__ == "Const" and isinstance( key.value, str): cls_locals[key.value].append(value) # Build the class from now. cls = mcs.__class__( name=name.value, lineno=caller.lineno, col_offset=caller.col_offset, parent=caller, ) empty = Pass() cls.postinit( bases=bases.elts, body=[empty], decorators=[], newstyle=True, metaclass=mcs, keywords=[], ) cls.locals = cls_locals return cls def infer_call_result(self, caller, context=None): context = bind_context_to_node(context, self.bound) if (self.bound.__class__.__name__ == "ClassDef" and self.bound.name == "type" and self.name == "__new__" and len(caller.args) == 4): # Check if we have a ``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().infer_call_result(caller, context) def bool_value(self, context=None): return True
class Instance(BaseInstance): """A special node representing a class instance.""" # pylint: disable=unnecessary-lambda special_attributes = lazy_descriptor(lambda: objectmodel.InstanceModel()) def __repr__(self): return "<Instance of {}.{} at 0x{}>".format(self._proxied.root().name, self._proxied.name, id(self)) def __str__(self): return f"Instance of {self._proxied.root().name}.{self._proxied.name}" def callable(self): try: self._proxied.getattr("__call__", class_context=False) return True except AttributeInferenceError: return False def pytype(self): return self._proxied.qname() def display_type(self): return "Instance of" def bool_value(self, context=None): """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 = context or InferenceContext() context.boundnode = self try: result = _infer_method_result_truth(self, BOOL_SPECIAL_METHOD, context) except (InferenceError, AttributeInferenceError): # Fallback to __len__. try: result = _infer_method_result_truth(self, "__len__", context) except (AttributeInferenceError, InferenceError): return True return result def getitem(self, index, context=None): # TODO: Rewrap index to Const for this case new_context = bind_context_to_node(context, self) if not context: context = new_context method = next(self.igetattr("__getitem__", context=context), None) # Create a new CallContext for providing index as an argument. new_context.callcontext = CallContext(args=[index], callee=method) if not isinstance(method, BoundMethod): raise InferenceError("Could not find __getitem__ for {node!r}.", node=self, context=context) if len(method.args.arguments) != 2: # (self, index) raise AstroidTypeError( "__getitem__ for {node!r} does not have correct signature", node=self, context=context, ) return next(method.infer_call_result(self, new_context), None)
class BoundMethod(UnboundMethod): """a special node representing a method bound to an instance""" # pylint: disable=unnecessary-lambda special_attributes = util.lazy_descriptor( lambda: objectmodel.BoundMethodModel()) def __init__(self, proxy, bound): UnboundMethod.__init__(self, proxy) self.bound = bound def is_bound(self): return True def _infer_type_new_call(self, caller, context): """Try to infer what type.__new__(mcs, name, bases, attrs) returns. In order for such call to be valid, the metaclass needs to be a subtype of ``type``, the name needs to be a string, the bases needs to be a tuple of classes and the attributes a dictionary of strings to values. """ from astroid import node_classes # Verify the metaclass mcs = next(caller.args[0].infer(context=context)) if mcs.__class__.__name__ != 'ClassDef': # Not a valid first argument. return if not mcs.is_subtype_of("%s.type" % BUILTINS): # Not a valid metaclass. return # Verify the name name = next(caller.args[1].infer(context=context)) if name.__class__.__name__ != 'Const': # Not a valid name, needs to be a const. return if not isinstance(name.value, str): # Needs to be a string. return # Verify the bases bases = next(caller.args[2].infer(context=context)) if bases.__class__.__name__ != 'Tuple': # Needs to be a tuple. return inferred_bases = [ next(elt.infer(context=context)) for elt in bases.elts ] if any(base.__class__.__name__ != 'ClassDef' for base in inferred_bases): # All the bases needs to be Classes return # Verify the attributes. attrs = next(caller.args[3].infer(context=context)) if attrs.__class__.__name__ != 'Dict': # Needs to be a dictionary. return cls_locals = collections.defaultdict(list) for key, value in attrs.items: key = next(key.infer(context=context)) value = next(value.infer(context=context)) if key.__class__.__name__ != 'Const': # Something invalid as an attribute. return if not isinstance(key.value, str): # Not a proper attribute. return cls_locals[key.value].append(value) # Build the class from now. cls = mcs.__class__(name=name.value, lineno=caller.lineno, col_offset=caller.col_offset, parent=caller) empty = node_classes.Pass() cls.postinit(bases=bases.elts, body=[empty], decorators=[], newstyle=True, metaclass=mcs, keywords=[]) cls.locals = cls_locals return cls 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 bool_value(self): return True
class UnboundMethod(Proxy): """a special node representing a method not bound to an instance""" # pylint: disable=unnecessary-lambda special_attributes = lazy_descriptor( lambda: objectmodel.UnboundMethodModel()) def __repr__(self): frame = self._proxied.parent.frame(future=True) return "<{} {} of {} at 0x{}".format(self.__class__.__name__, self._proxied.name, frame.qname(), id(self)) def implicit_parameters(self) -> Literal[0]: return 0 def is_bound(self): return False def getattr(self, name, context=None): if name in self.special_attributes: return [self.special_attributes.lookup(name)] return self._proxied.getattr(name, context) def igetattr(self, name, context=None): if name in self.special_attributes: return iter((self.special_attributes.lookup(name), )) return self._proxied.igetattr(name, context) def infer_call_result(self, caller, context): """ The boundnode of the regular context with a function called on ``object.__new__`` will be of type ``object``, which is incorrect for the argument in general. If no context is given the ``object.__new__`` call argument will be correctly inferred except when inside a call that requires the additional context (such as a classmethod) of the boundnode to determine which class the method was called from """ # If we're unbound method __new__ of a builtin, the result is an # instance of the class given as first argument. if self._proxied.name == "__new__": qname = self._proxied.parent.frame(future=True).qname() # Avoid checking builtins.type: _infer_type_new_call() does more validation if qname.startswith("builtins.") and qname != "builtins.type": return self._infer_builtin_new(caller, context) return self._proxied.infer_call_result(caller, context) def _infer_builtin_new( self, caller: nodes.Call, context: InferenceContext, ) -> collections.abc.Generator[nodes.Const | Instance | type[Uninferable], None, None]: if not caller.args: return # Attempt to create a constant if len(caller.args) > 1: value = None if isinstance(caller.args[1], nodes.Const): value = caller.args[1].value else: inferred_arg = next(caller.args[1].infer(), None) if isinstance(inferred_arg, nodes.Const): value = inferred_arg.value if value is not None: yield nodes.const_factory(value) return node_context = context.extra_context.get(caller.args[0]) for inferred in caller.args[0].infer(context=node_context): if inferred is Uninferable: yield inferred if isinstance(inferred, nodes.ClassDef): yield Instance(inferred) raise InferenceError def bool_value(self, context=None): return True