| | """ |
| | 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): |
| | |
| | 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): |
| | |
| | |
| | |
| | |
| | |
| | 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): |
| | |
| | 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 |
| | |
| | |
| | for lazy_cls in self.py__bases__(): |
| | |
| | |
| | for cls in lazy_cls.infer(): |
| | |
| | |
| | try: |
| | mro_method = cls.py__mro__ |
| | except AttributeError: |
| | |
| | """ |
| | >>> 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: |
| | |
| | |
| | |
| | args = ValuesArguments([]) |
| | for instance in type_.py__call__(args): |
| | instance_filters = instance.get_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): |
| | |
| | |
| | |
| | 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): |
| | |
| | |
| | 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 |
| | |
| | |
| | |
| | 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: |
| | |
| | |
| | |
| | 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 |
| |
|
| | 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: |
| | |
| | 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 [] |
| |
|