You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			391 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			391 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
Like described in the :mod:`parso.python.tree` module,
 | 
						|
there's a need for an ast like module to represent the states of parsed
 | 
						|
modules.
 | 
						|
 | 
						|
But now there are also structures in Python that need a little bit more than
 | 
						|
that. An ``Instance`` for example is only a ``Class`` before it is
 | 
						|
instantiated. This class represents these cases.
 | 
						|
 | 
						|
So, why is there also a ``Class`` class here? Well, there are decorators and
 | 
						|
they change classes in Python 3.
 | 
						|
 | 
						|
Representation modules also define "magic methods". Those methods look like
 | 
						|
``py__foo__`` and are typically mappable to the Python equivalents ``__call__``
 | 
						|
and others. Here's a list:
 | 
						|
 | 
						|
====================================== ========================================
 | 
						|
**Method**                             **Description**
 | 
						|
-------------------------------------- ----------------------------------------
 | 
						|
py__call__(arguments: Array)           On callable objects, returns types.
 | 
						|
py__bool__()                           Returns True/False/None; None means that
 | 
						|
                                       there's no certainty.
 | 
						|
py__bases__()                          Returns a list of base classes.
 | 
						|
py__iter__()                           Returns a generator of a set of types.
 | 
						|
py__class__()                          Returns the class of an instance.
 | 
						|
py__simple_getitem__(index: int/str)   Returns a a set of types of the index.
 | 
						|
                                       Can raise an IndexError/KeyError.
 | 
						|
py__getitem__(indexes: ValueSet)       Returns a a set of types of the index.
 | 
						|
py__file__()                           Only on modules. Returns None if does
 | 
						|
                                       not exist.
 | 
						|
py__package__() -> List[str]           Only on modules. For the import system.
 | 
						|
py__path__()                           Only on modules. For the import system.
 | 
						|
py__get__(call_object)                 Only on instances. Simulates
 | 
						|
                                       descriptors.
 | 
						|
py__doc__()                            Returns the docstring for a value.
 | 
						|
====================================== ========================================
 | 
						|
 | 
						|
"""
 | 
						|
from jedi import debug
 | 
						|
from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \
 | 
						|
    function_is_property
 | 
						|
from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \
 | 
						|
    inference_state_method_generator_cache
 | 
						|
from jedi.inference import compiled
 | 
						|
from jedi.inference.lazy_value import LazyKnownValues, LazyTreeValue
 | 
						|
from jedi.inference.filters import ParserTreeFilter
 | 
						|
from jedi.inference.names import TreeNameDefinition, ValueName
 | 
						|
from jedi.inference.arguments import unpack_arglist, ValuesArguments
 | 
						|
from jedi.inference.base_value import ValueSet, iterator_to_value_set, \
 | 
						|
    NO_VALUES
 | 
						|
from jedi.inference.context import ClassContext
 | 
						|
from jedi.inference.value.function import FunctionAndClassBase
 | 
						|
from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager
 | 
						|
from jedi.plugins import plugin_manager
 | 
						|
 | 
						|
 | 
						|
class ClassName(TreeNameDefinition):
 | 
						|
    def __init__(self, class_value, tree_name, name_context, apply_decorators):
 | 
						|
        super().__init__(name_context, tree_name)
 | 
						|
        self._apply_decorators = apply_decorators
 | 
						|
        self._class_value = class_value
 | 
						|
 | 
						|
    @iterator_to_value_set
 | 
						|
    def infer(self):
 | 
						|
        # We're using a different value to infer, so we cannot call super().
 | 
						|
        from jedi.inference.syntax_tree import tree_name_to_values
 | 
						|
        inferred = tree_name_to_values(
 | 
						|
            self.parent_context.inference_state, self.parent_context, self.tree_name)
 | 
						|
 | 
						|
        for result_value in inferred:
 | 
						|
            if self._apply_decorators:
 | 
						|
                yield from result_value.py__get__(instance=None, class_value=self._class_value)
 | 
						|
            else:
 | 
						|
                yield result_value
 | 
						|
 | 
						|
    @property
 | 
						|
    def api_type(self):
 | 
						|
        type_ = super().api_type
 | 
						|
        if type_ == 'function':
 | 
						|
            definition = self.tree_name.get_definition()
 | 
						|
            if definition is None:
 | 
						|
                return type_
 | 
						|
            if function_is_property(definition):
 | 
						|
                # This essentially checks if there is an @property before
 | 
						|
                # the function. @property could be something different, but
 | 
						|
                # any programmer that redefines property as something that
 | 
						|
                # is not really a property anymore, should be shot. (i.e.
 | 
						|
                # this is a heuristic).
 | 
						|
                return 'property'
 | 
						|
        return type_
 | 
						|
 | 
						|
 | 
						|
class ClassFilter(ParserTreeFilter):
 | 
						|
    def __init__(self, class_value, node_context=None, until_position=None,
 | 
						|
                 origin_scope=None, is_instance=False):
 | 
						|
        super().__init__(
 | 
						|
            class_value.as_context(), node_context,
 | 
						|
            until_position=until_position,
 | 
						|
            origin_scope=origin_scope,
 | 
						|
        )
 | 
						|
        self._class_value = class_value
 | 
						|
        self._is_instance = is_instance
 | 
						|
 | 
						|
    def _convert_names(self, names):
 | 
						|
        return [
 | 
						|
            ClassName(
 | 
						|
                class_value=self._class_value,
 | 
						|
                tree_name=name,
 | 
						|
                name_context=self._node_context,
 | 
						|
                apply_decorators=not self._is_instance,
 | 
						|
            ) for name in names
 | 
						|
        ]
 | 
						|
 | 
						|
    def _equals_origin_scope(self):
 | 
						|
        node = self._origin_scope
 | 
						|
        while node is not None:
 | 
						|
            if node == self._parser_scope or node == self.parent_context:
 | 
						|
                return True
 | 
						|
            node = get_cached_parent_scope(self._parso_cache_node, node)
 | 
						|
        return False
 | 
						|
 | 
						|
    def _access_possible(self, name):
 | 
						|
        # Filter for name mangling of private variables like __foo
 | 
						|
        return not name.value.startswith('__') or name.value.endswith('__') \
 | 
						|
            or self._equals_origin_scope()
 | 
						|
 | 
						|
    def _filter(self, names):
 | 
						|
        names = super()._filter(names)
 | 
						|
        return [name for name in names if self._access_possible(name)]
 | 
						|
 | 
						|
 | 
						|
class ClassMixin:
 | 
						|
    def is_class(self):
 | 
						|
        return True
 | 
						|
 | 
						|
    def is_class_mixin(self):
 | 
						|
        return True
 | 
						|
 | 
						|
    def py__call__(self, arguments):
 | 
						|
        from jedi.inference.value import TreeInstance
 | 
						|
 | 
						|
        from jedi.inference.gradual.typing import TypedDict
 | 
						|
        if self.is_typeddict():
 | 
						|
            return ValueSet([TypedDict(self)])
 | 
						|
        return ValueSet([TreeInstance(self.inference_state, self.parent_context, self, arguments)])
 | 
						|
 | 
						|
    def py__class__(self):
 | 
						|
        return compiled.builtin_from_name(self.inference_state, 'type')
 | 
						|
 | 
						|
    @property
 | 
						|
    def name(self):
 | 
						|
        return ValueName(self, self.tree_node.name)
 | 
						|
 | 
						|
    def py__name__(self):
 | 
						|
        return self.name.string_name
 | 
						|
 | 
						|
    @inference_state_method_generator_cache()
 | 
						|
    def py__mro__(self):
 | 
						|
        mro = [self]
 | 
						|
        yield self
 | 
						|
        # TODO Do a proper mro resolution. Currently we are just listing
 | 
						|
        # classes. However, it's a complicated algorithm.
 | 
						|
        for lazy_cls in self.py__bases__():
 | 
						|
            # TODO there's multiple different mro paths possible if this yields
 | 
						|
            # multiple possibilities. Could be changed to be more correct.
 | 
						|
            for cls in lazy_cls.infer():
 | 
						|
                # TODO detect for TypeError: duplicate base class str,
 | 
						|
                # e.g.  `class X(str, str): pass`
 | 
						|
                try:
 | 
						|
                    mro_method = cls.py__mro__
 | 
						|
                except AttributeError:
 | 
						|
                    # TODO add a TypeError like:
 | 
						|
                    """
 | 
						|
                    >>> class Y(lambda: test): pass
 | 
						|
                    Traceback (most recent call last):
 | 
						|
                      File "<stdin>", line 1, in <module>
 | 
						|
                    TypeError: function() argument 1 must be code, not str
 | 
						|
                    >>> class Y(1): pass
 | 
						|
                    Traceback (most recent call last):
 | 
						|
                      File "<stdin>", line 1, in <module>
 | 
						|
                    TypeError: int() takes at most 2 arguments (3 given)
 | 
						|
                    """
 | 
						|
                    debug.warning('Super class of %s is not a class: %s', self, cls)
 | 
						|
                else:
 | 
						|
                    for cls_new in mro_method():
 | 
						|
                        if cls_new not in mro:
 | 
						|
                            mro.append(cls_new)
 | 
						|
                            yield cls_new
 | 
						|
 | 
						|
    def get_filters(self, origin_scope=None, is_instance=False,
 | 
						|
                    include_metaclasses=True, include_type_when_class=True):
 | 
						|
        if include_metaclasses:
 | 
						|
            metaclasses = self.get_metaclasses()
 | 
						|
            if metaclasses:
 | 
						|
                yield from self.get_metaclass_filters(metaclasses, is_instance)
 | 
						|
 | 
						|
        for cls in self.py__mro__():
 | 
						|
            if cls.is_compiled():
 | 
						|
                yield from cls.get_filters(is_instance=is_instance)
 | 
						|
            else:
 | 
						|
                yield ClassFilter(
 | 
						|
                    self, node_context=cls.as_context(),
 | 
						|
                    origin_scope=origin_scope,
 | 
						|
                    is_instance=is_instance
 | 
						|
                )
 | 
						|
        if not is_instance and include_type_when_class:
 | 
						|
            from jedi.inference.compiled import builtin_from_name
 | 
						|
            type_ = builtin_from_name(self.inference_state, 'type')
 | 
						|
            assert isinstance(type_, ClassValue)
 | 
						|
            if type_ != self:
 | 
						|
                # We are not using execute_with_values here, because the
 | 
						|
                # plugin function for type would get executed instead of an
 | 
						|
                # instance creation.
 | 
						|
                args = ValuesArguments([])
 | 
						|
                for instance in type_.py__call__(args):
 | 
						|
                    instance_filters = instance.get_filters()
 | 
						|
                    # Filter out self filters
 | 
						|
                    next(instance_filters, None)
 | 
						|
                    next(instance_filters, None)
 | 
						|
                    x = next(instance_filters, None)
 | 
						|
                    assert x is not None
 | 
						|
                    yield x
 | 
						|
 | 
						|
    def get_signatures(self):
 | 
						|
        # Since calling staticmethod without a function is illegal, the Jedi
 | 
						|
        # plugin doesn't return anything. Therefore call directly and get what
 | 
						|
        # we want: An instance of staticmethod.
 | 
						|
        metaclasses = self.get_metaclasses()
 | 
						|
        if metaclasses:
 | 
						|
            sigs = self.get_metaclass_signatures(metaclasses)
 | 
						|
            if sigs:
 | 
						|
                return sigs
 | 
						|
        args = ValuesArguments([])
 | 
						|
        init_funcs = self.py__call__(args).py__getattribute__('__init__')
 | 
						|
        return [sig.bind(self) for sig in init_funcs.get_signatures()]
 | 
						|
 | 
						|
    def _as_context(self):
 | 
						|
        return ClassContext(self)
 | 
						|
 | 
						|
    def get_type_hint(self, add_class_info=True):
 | 
						|
        if add_class_info:
 | 
						|
            return 'Type[%s]' % self.py__name__()
 | 
						|
        return self.py__name__()
 | 
						|
 | 
						|
    @inference_state_method_cache(default=False)
 | 
						|
    def is_typeddict(self):
 | 
						|
        # TODO Do a proper mro resolution. Currently we are just listing
 | 
						|
        # classes. However, it's a complicated algorithm.
 | 
						|
        from jedi.inference.gradual.typing import TypedDictClass
 | 
						|
        for lazy_cls in self.py__bases__():
 | 
						|
            if not isinstance(lazy_cls, LazyTreeValue):
 | 
						|
                return False
 | 
						|
            tree_node = lazy_cls.data
 | 
						|
            # Only resolve simple classes, stuff like Iterable[str] are more
 | 
						|
            # intensive to resolve and if generics are involved, we know it's
 | 
						|
            # not a TypedDict.
 | 
						|
            if not expr_is_dotted(tree_node):
 | 
						|
                return False
 | 
						|
 | 
						|
            for cls in lazy_cls.infer():
 | 
						|
                if isinstance(cls, TypedDictClass):
 | 
						|
                    return True
 | 
						|
                try:
 | 
						|
                    method = cls.is_typeddict
 | 
						|
                except AttributeError:
 | 
						|
                    # We're only dealing with simple classes, so just returning
 | 
						|
                    # here should be fine. This only happens with e.g. compiled
 | 
						|
                    # classes.
 | 
						|
                    return False
 | 
						|
                else:
 | 
						|
                    if method():
 | 
						|
                        return True
 | 
						|
        return False
 | 
						|
 | 
						|
    def py__getitem__(self, index_value_set, contextualized_node):
 | 
						|
        from jedi.inference.gradual.base import GenericClass
 | 
						|
        if not index_value_set:
 | 
						|
            debug.warning('Class indexes inferred to nothing. Returning class instead')
 | 
						|
            return ValueSet([self])
 | 
						|
        return ValueSet(
 | 
						|
            GenericClass(
 | 
						|
                self,
 | 
						|
                LazyGenericManager(
 | 
						|
                    context_of_index=contextualized_node.context,
 | 
						|
                    index_value=index_value,
 | 
						|
                )
 | 
						|
            )
 | 
						|
            for index_value in index_value_set
 | 
						|
        )
 | 
						|
 | 
						|
    def with_generics(self, generics_tuple):
 | 
						|
        from jedi.inference.gradual.base import GenericClass
 | 
						|
        return GenericClass(
 | 
						|
            self,
 | 
						|
            TupleGenericManager(generics_tuple)
 | 
						|
        )
 | 
						|
 | 
						|
    def define_generics(self, type_var_dict):
 | 
						|
        from jedi.inference.gradual.base import GenericClass
 | 
						|
 | 
						|
        def remap_type_vars():
 | 
						|
            """
 | 
						|
            The TypeVars in the resulting classes have sometimes different names
 | 
						|
            and we need to check for that, e.g. a signature can be:
 | 
						|
 | 
						|
            def iter(iterable: Iterable[_T]) -> Iterator[_T]: ...
 | 
						|
 | 
						|
            However, the iterator is defined as Iterator[_T_co], which means it has
 | 
						|
            a different type var name.
 | 
						|
            """
 | 
						|
            for type_var in self.list_type_vars():
 | 
						|
                yield type_var_dict.get(type_var.py__name__(), NO_VALUES)
 | 
						|
 | 
						|
        if type_var_dict:
 | 
						|
            return ValueSet([GenericClass(
 | 
						|
                self,
 | 
						|
                TupleGenericManager(tuple(remap_type_vars()))
 | 
						|
            )])
 | 
						|
        return ValueSet({self})
 | 
						|
 | 
						|
 | 
						|
class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
 | 
						|
    api_type = 'class'
 | 
						|
 | 
						|
    @inference_state_method_cache()
 | 
						|
    def list_type_vars(self):
 | 
						|
        found = []
 | 
						|
        arglist = self.tree_node.get_super_arglist()
 | 
						|
        if arglist is None:
 | 
						|
            return []
 | 
						|
 | 
						|
        for stars, node in unpack_arglist(arglist):
 | 
						|
            if stars:
 | 
						|
                continue  # These are not relevant for this search.
 | 
						|
 | 
						|
            from jedi.inference.gradual.annotation import find_unknown_type_vars
 | 
						|
            for type_var in find_unknown_type_vars(self.parent_context, node):
 | 
						|
                if type_var not in found:
 | 
						|
                    # The order matters and it's therefore a list.
 | 
						|
                    found.append(type_var)
 | 
						|
        return found
 | 
						|
 | 
						|
    def _get_bases_arguments(self):
 | 
						|
        arglist = self.tree_node.get_super_arglist()
 | 
						|
        if arglist:
 | 
						|
            from jedi.inference import arguments
 | 
						|
            return arguments.TreeArguments(self.inference_state, self.parent_context, arglist)
 | 
						|
        return None
 | 
						|
 | 
						|
    @inference_state_method_cache(default=())
 | 
						|
    def py__bases__(self):
 | 
						|
        args = self._get_bases_arguments()
 | 
						|
        if args is not None:
 | 
						|
            lst = [value for key, value in args.unpack() if key is None]
 | 
						|
            if lst:
 | 
						|
                return lst
 | 
						|
 | 
						|
        if self.py__name__() == 'object' \
 | 
						|
                and self.parent_context.is_builtins_module():
 | 
						|
            return []
 | 
						|
        return [LazyKnownValues(
 | 
						|
            self.inference_state.builtins_module.py__getattribute__('object')
 | 
						|
        )]
 | 
						|
 | 
						|
    @plugin_manager.decorate()
 | 
						|
    def get_metaclass_filters(self, metaclasses, is_instance):
 | 
						|
        debug.warning('Unprocessed metaclass %s', metaclasses)
 | 
						|
        return []
 | 
						|
 | 
						|
    @inference_state_method_cache(default=NO_VALUES)
 | 
						|
    def get_metaclasses(self):
 | 
						|
        args = self._get_bases_arguments()
 | 
						|
        if args is not None:
 | 
						|
            m = [value for key, value in args.unpack() if key == 'metaclass']
 | 
						|
            metaclasses = ValueSet.from_sets(lazy_value.infer() for lazy_value in m)
 | 
						|
            metaclasses = ValueSet(m for m in metaclasses if m.is_class())
 | 
						|
            if metaclasses:
 | 
						|
                return metaclasses
 | 
						|
 | 
						|
        for lazy_base in self.py__bases__():
 | 
						|
            for value in lazy_base.infer():
 | 
						|
                if value.is_class():
 | 
						|
                    values = value.get_metaclasses()
 | 
						|
                    if values:
 | 
						|
                        return values
 | 
						|
        return NO_VALUES
 | 
						|
 | 
						|
    @plugin_manager.decorate()
 | 
						|
    def get_metaclass_signatures(self, metaclasses):
 | 
						|
        return []
 |