Example #1
0
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)
Example #2
0
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})"
Example #3
0
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, ))
Example #4
0
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
Example #5
0
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
Example #6
0
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
Example #7
0
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())
Example #8
0
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())
Example #9
0
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
Example #10
0
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))
Example #11
0
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
Example #12
0
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)
Example #13
0
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
Example #14
0
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