from abc import abstractproperty
from jedi import debug
from jedi.evaluate import compiled
from jedi.evaluate import filters
from jedi.evaluate.base_context import Context, NO_CONTEXTS, ContextSet, \
iterator_to_context_set
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.arguments import AbstractArguments, AnonymousArguments
from jedi.cache import memoize_method
from jedi.evaluate.context.function import FunctionExecutionContext, FunctionContext
from jedi.evaluate.context.klass import ClassContext, apply_py__get__
from jedi.evaluate.context import iterable
from jedi.parser_utils import get_parent_scope
class BaseInstanceFunctionExecution(FunctionExecutionContext):
def __init__(self, instance, *args, **kwargs):
self.instance = instance
super(BaseInstanceFunctionExecution, self).__init__(
instance.evaluator, *args, **kwargs)
class InstanceFunctionExecution(BaseInstanceFunctionExecution):
def __init__(self, instance, parent_context, function_context, var_args):
var_args = InstanceVarArgs(self, var_args)
super(InstanceFunctionExecution, self).__init__(
instance, parent_context, function_context, var_args)
class AnonymousInstanceFunctionExecution(BaseInstanceFunctionExecution):
function_execution_filter = filters.AnonymousInstanceFunctionExecutionFilter
def __init__(self, instance, parent_context, function_context, var_args):
super(AnonymousInstanceFunctionExecution, self).__init__(
instance, parent_context, function_context, var_args)
class AbstractInstanceContext(Context):
"""
This class is used to evaluate instances.
"""
api_type = u'instance'
function_execution_cls = InstanceFunctionExecution
def __init__(self, evaluator, parent_context, class_context, var_args):
super(AbstractInstanceContext, self).__init__(evaluator, parent_context)
# Generated instances are classes that are just generated by self
# (No var_args) used.
self.class_context = class_context
self.var_args = var_args
def is_class(self):
return False
@property
def py__call__(self):
names = self.get_function_slot_names(u'__call__')
if not names:
# Means the Instance is not callable.
raise AttributeError
def execute(arguments):
return ContextSet.from_sets(name.execute(arguments) for name in names)
return execute
def py__class__(self):
return self.class_context
def py__bool__(self):
# Signalize that we don't know about the bool type.
return None
def get_function_slot_names(self, name):
# Python classes don't look at the dictionary of the instance when
# looking up `__call__`. This is something that has to do with Python's
# internal slot system (note: not __slots__, but C slots).
for filter in self.get_filters(include_self_names=False):
names = filter.get(name)
if names:
return names
return []
def execute_function_slots(self, names, *evaluated_args):
return ContextSet.from_sets(
name.execute_evaluated(*evaluated_args)
for name in names
)
def py__get__(self, obj):
# Arguments in __get__ descriptors are obj, class.
# `method` is the new parent of the array, don't know if that's good.
names = self.get_function_slot_names(u'__get__')
if names:
if isinstance(obj, AbstractInstanceContext):
return self.execute_function_slots(names, obj, obj.class_context)
else:
none_obj = compiled.builtin_from_name(self.evaluator, u'None')
return self.execute_function_slots(names, none_obj, obj)
else:
return ContextSet(self)
def get_filters(self, search_global=None, until_position=None,
origin_scope=None, include_self_names=True):
if include_self_names:
for cls in self.class_context.py__mro__():
if not isinstance(cls, compiled.CompiledObject) \
or cls.tree_node is not None:
# In this case we're excluding compiled objects that are
# not fake objects. It doesn't make sense for normal
# compiled objects to search for self variables.
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
for cls in self.class_context.py__mro__():
if isinstance(cls, compiled.CompiledObject):
yield CompiledInstanceClassFilter(self.evaluator, self, cls)
else:
yield InstanceClassFilter(self.evaluator, self, cls, origin_scope)
def py__getitem__(self, index):
try:
names = self.get_function_slot_names(u'__getitem__')
except KeyError:
debug.warning('No __getitem__, cannot access the array.')
return NO_CONTEXTS
else:
index_obj = compiled.create_simple_object(self.evaluator, index)
return self.execute_function_slots(names, index_obj)
def py__iter__(self):
iter_slot_names = self.get_function_slot_names(u'__iter__')
if not iter_slot_names:
debug.warning('No __iter__ on %s.' % self)
return
for generator in self.execute_function_slots(iter_slot_names):
if isinstance(generator, AbstractInstanceContext):
# `__next__` logic.
if self.evaluator.environment.version_info.major == 2:
name = u'next'
else:
name = u'__next__'
iter_slot_names = generator.get_function_slot_names(name)
if iter_slot_names:
yield LazyKnownContexts(
generator.execute_function_slots(iter_slot_names)
)
else:
debug.warning('Instance has no __next__ function in %s.', generator)
else:
for lazy_context in generator.py__iter__():
yield lazy_context
@abstractproperty
def name(self):
pass
def _create_init_execution(self, class_context, func_node):
bound_method = BoundMethod(
self.evaluator, self, class_context, self.parent_context, func_node
)
return self.function_execution_cls(
self,
class_context.parent_context,
bound_method,
self.var_args
)
def create_init_executions(self):
for name in self.get_function_slot_names(u'__init__'):
if isinstance(name, SelfName):
yield self._create_init_execution(name.class_context, name.tree_name.parent)
@evaluator_method_cache()
def create_instance_context(self, class_context, node):
if node.parent.type in ('funcdef', 'classdef'):
node = node.parent
scope = get_parent_scope(node)
if scope == class_context.tree_node:
return class_context
else:
parent_context = self.create_instance_context(class_context, scope)
if scope.type == 'funcdef':
if scope.name.value == '__init__' and parent_context == class_context:
return self._create_init_execution(class_context, scope)
else:
bound_method = BoundMethod(
self.evaluator, self, class_context,
parent_context, scope
)
return bound_method.get_function_execution()
elif scope.type == 'classdef':
class_context = ClassContext(self.evaluator, parent_context, scope)
return class_context
elif scope.type == 'comp_for':
# Comprehensions currently don't have a special scope in Jedi.
return self.create_instance_context(class_context, scope)
else:
raise NotImplementedError
return class_context
def __repr__(self):
return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_context,
self.var_args)
class CompiledInstance(AbstractInstanceContext):
def __init__(self, *args, **kwargs):
super(CompiledInstance, self).__init__(*args, **kwargs)
# I don't think that dynamic append lookups should happen here. That
# sounds more like something that should go to py__iter__.
self._original_var_args = self.var_args
if self.class_context.name.string_name in ['list', 'set'] \
and self.parent_context.get_root_context() == self.evaluator.builtins_module:
# compare the module path with the builtin name.
self.var_args = iterable.get_dynamic_array_instance(self)
@property
def name(self):
return compiled.CompiledContextName(self, self.class_context.name.string_name)
def create_instance_context(self, class_context, node):
if get_parent_scope(node).type == 'classdef':
return class_context
else:
return super(CompiledInstance, self).create_instance_context(class_context, node)
def get_first_non_keyword_argument_contexts(self):
key, lazy_context = next(self._original_var_args.unpack(), ('', None))
if key is not None:
return NO_CONTEXTS
return lazy_context.infer()
class TreeInstance(AbstractInstanceContext):
def __init__(self, evaluator, parent_context, class_context, var_args):
super(TreeInstance, self).__init__(evaluator, parent_context,
class_context, var_args)
self.tree_node = class_context.tree_node
@property
def name(self):
return filters.ContextName(self, self.class_context.name.tree_name)
class AnonymousInstance(TreeInstance):
function_execution_cls = AnonymousInstanceFunctionExecution
def __init__(self, evaluator, parent_context, class_context):
super(AnonymousInstance, self).__init__(
evaluator,
parent_context,
class_context,
var_args=AnonymousArguments(),
)
class CompiledInstanceName(compiled.CompiledName):
def __init__(self, evaluator, instance, parent_context, name):
super(CompiledInstanceName, self).__init__(evaluator, parent_context, name)
self._instance = instance
@iterator_to_context_set
def infer(self):
for result_context in super(CompiledInstanceName, self).infer():
is_function = result_context.api_type == 'function'
if result_context.tree_node is not None and is_function:
parent_context = result_context.parent_context
while parent_context.is_class():
parent_context = parent_context.parent_context
yield BoundMethod(
result_context.evaluator, self._instance, self.parent_context,
parent_context, result_context.tree_node
)
else:
if is_function:
yield CompiledBoundMethod(result_context)
else:
yield result_context
class CompiledInstanceClassFilter(compiled.CompiledObjectFilter):
name_class = CompiledInstanceName
def __init__(self, evaluator, instance, compiled_object):
super(CompiledInstanceClassFilter, self).__init__(
evaluator,
compiled_object,
is_instance=True,
)
self._instance = instance
def _create_name(self, name):
return self.name_class(
self._evaluator, self._instance, self._compiled_object, name)
class BoundMethod(FunctionContext):
def __init__(self, evaluator, instance, class_context, *args, **kwargs):
super(BoundMethod, self).__init__(evaluator, *args, **kwargs)
self._instance = instance
self._class_context = class_context
def get_function_execution(self, arguments=None):
if arguments is None:
arguments = AnonymousArguments()
return AnonymousInstanceFunctionExecution(
self._instance, self.parent_context, self, arguments)
else:
return InstanceFunctionExecution(
self._instance, self.parent_context, self, arguments)
class CompiledBoundMethod(compiled.CompiledObject):
def __init__(self, func):
super(CompiledBoundMethod, self).__init__(
func.evaluator, func.access_handle, func.parent_context, func.tree_node)
def get_param_names(self):
return list(super(CompiledBoundMethod, self).get_param_names())[1:]
class InstanceNameDefinition(filters.TreeNameDefinition):
def infer(self):
return super(InstanceNameDefinition, self).infer()
class SelfName(filters.TreeNameDefinition):
"""
This name calculates the parent_context lazily.
"""
def __init__(self, instance, class_context, tree_name):
self._instance = instance
self.class_context = class_context
self.tree_name = tree_name
@property
def parent_context(self):
return self._instance.create_instance_context(self.class_context, self.tree_name)
class LazyInstanceClassName(SelfName):
@iterator_to_context_set
def infer(self):
for result_context in super(LazyInstanceClassName, self).infer():
if isinstance(result_context, FunctionContext):
# Classes are never used to resolve anything within the
# functions. Only other functions and modules will resolve
# those things.
parent_context = result_context.parent_context
while parent_context.is_class():
parent_context = parent_context.parent_context
yield BoundMethod(
result_context.evaluator, self._instance, self.class_context,
parent_context, result_context.tree_node
)
else:
for c in apply_py__get__(result_context, self._instance):
yield c
class InstanceClassFilter(filters.ParserTreeFilter):
name_class = LazyInstanceClassName
def __init__(self, evaluator, context, class_context, origin_scope):
super(InstanceClassFilter, self).__init__(
evaluator=evaluator,
context=context,
node_context=class_context,
origin_scope=origin_scope
)
self._class_context = class_context
def _equals_origin_scope(self):
node = self._origin_scope
while node is not None:
if node == self._parser_scope or node == self.context:
return True
node = get_parent_scope(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(InstanceClassFilter, self)._filter(names)
return [name for name in names if self._access_possible(name)]
def _convert_names(self, names):
return [self.name_class(self.context, self._class_context, name) for name in names]
class SelfAttributeFilter(InstanceClassFilter):
"""
This class basically filters all the use cases where `self.*` was assigned.
"""
name_class = SelfName
def _filter(self, names):
names = self._filter_self_names(names)
if isinstance(self._parser_scope, compiled.CompiledObject) and False:
# This would be for builtin skeletons, which are not yet supported.
return list(names)
else:
start, end = self._parser_scope.start_pos, self._parser_scope.end_pos
return [n for n in names if start < n.start_pos < end]
def _filter_self_names(self, names):
for name in names:
trailer = name.parent
if trailer.type == 'trailer' \
and len(trailer.children) == 2 \
and trailer.children[0] == '.':
if name.is_definition() and self._access_possible(name):
yield name
def _check_flows(self, names):
return names
class InstanceVarArgs(AbstractArguments):
def __init__(self, execution_context, var_args):
self._execution_context = execution_context
self._var_args = var_args
@memoize_method
def _get_var_args(self):
return self._var_args
@property
def argument_node(self):
return self._var_args.argument_node
@property
def trailer(self):
return self._var_args.trailer
def unpack(self, func=None):
yield None, LazyKnownContext(self._execution_context.instance)
for values in self._get_var_args().unpack(func):
yield values
def get_calling_nodes(self):
return self._get_var_args().get_calling_nodes()