diff --git a/jedi/api/completion.py b/jedi/api/completion.py index dbec8fa6..9eb91604 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -7,7 +7,7 @@ from jedi.api import helpers from jedi.evaluate import imports from jedi.api import keywords from jedi.evaluate import compiled -from jedi.evaluate.helpers import call_of_leaf +from jedi.evaluate.helpers import evaluate_call_of_leaf from jedi.evaluate.filters import get_global_filters @@ -164,8 +164,7 @@ class Completion: return list(self._get_class_context_completions(is_function=True)) elif symbol_names[-1] == 'trailer' and nodes[-1] == '.': dot = self._module_node.get_leaf_for_position(self._position) - atom_expr = call_of_leaf(dot.get_previous_leaf()) - completion_names += self._trailer_completions(atom_expr) + completion_names += self._trailer_completions(dot.get_previous_leaf()) else: completion_names += self._global_completions() completion_names += self._get_class_context_completions(is_function=False) @@ -194,10 +193,12 @@ class Completion: completion_names += filter.values() return completion_names - def _trailer_completions(self, atom_expr): + def _trailer_completions(self, previous_leaf): user_context = get_user_scope(self._module_context, self._position) - evaluation_context = self._evaluator.create_context(self._module_context, atom_expr) - contexts = self._evaluator.eval_element(evaluation_context, atom_expr) + evaluation_context = self._evaluator.create_context( + self._module_context, previous_leaf + ) + contexts = evaluate_call_of_leaf(evaluation_context, previous_leaf) completion_names = [] debug.dbg('trailer completion contexts: %s', contexts) for context in contexts: diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index 7d8702b8..423f2f7e 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -5,7 +5,7 @@ import re from collections import namedtuple from jedi._compatibility import u -from jedi.evaluate.helpers import call_of_leaf +from jedi.evaluate.helpers import evaluate_call_of_leaf from jedi import parser from jedi.parser import tokenize from jedi.cache import time_cache @@ -198,16 +198,12 @@ def evaluate_goto_definition(evaluator, context, leaf): # magic itself. return evaluator.goto_definitions(context, leaf) - node = None parent = leaf.parent if parent.type == 'atom': - node = leaf.parent + return context.eval_node(leaf.parent) elif parent.type == 'trailer': - node = call_of_leaf(leaf) - - if node is None: - return [] - return evaluator.eval_element(context, node) + return evaluate_call_of_leaf(context, leaf) + return [] CallSignatureDetails = namedtuple( diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 0e110e3d..db4b3461 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -429,8 +429,7 @@ class Evaluator(object): elif def_.type in ('import_from', 'import_name'): return imports.ImportWrapper(context, name).follow() - call = helpers.call_of_leaf(name) - return self.eval_element(context, call) + return helpers.evaluate_call_of_leaf(context, name) def goto(self, context, name): def resolve_implicit_imports(names): @@ -522,7 +521,7 @@ class Evaluator(object): raise DeprecationWarning return element - def create_context(self, module_context, node): + def create_context(self, base_context, node): def from_scope_node(scope_node, child_is_funcdef=None): is_funcdef = scope_node.type == 'funcdef' parent_context = None @@ -531,8 +530,8 @@ class Evaluator(object): parent_context = from_scope_node(parent_scope, child_is_funcdef=is_funcdef) # TODO this whole procedure just ignores decorators - if scope_node == module_context.module_node: - return module_context + if scope_node == base_node: + return base_context elif is_funcdef: if isinstance(parent_context, AnonymousInstance): return AnonymousInstanceFunctionExecution( @@ -550,6 +549,8 @@ class Evaluator(object): else: return class_context + base_node = base_context.get_node() + if node.is_scope(): scope_node = node else: diff --git a/jedi/evaluate/context.py b/jedi/evaluate/context.py index 7fa52849..a528f761 100644 --- a/jedi/evaluate/context.py +++ b/jedi/evaluate/context.py @@ -43,6 +43,9 @@ class Context(object): def eval_stmt(self, stmt, seek_name=None): return self.evaluator.eval_statement(self, stmt, seek_name) + def eval_trailer(self, types, trailer): + return self.evaluator.eval_trailer(self, types, trailer) + class TreeContext(Context): pass diff --git a/jedi/evaluate/dynamic.py b/jedi/evaluate/dynamic.py index 6458168f..e2649539 100644 --- a/jedi/evaluate/dynamic.py +++ b/jedi/evaluate/dynamic.py @@ -16,9 +16,7 @@ It works as follows: - search for function calls named ``foo`` - execute these calls and check the input. This work with a ``ParamListener``. """ -from itertools import chain -from jedi._compatibility import unicode from jedi.parser import tree from jedi import settings from jedi import debug @@ -26,7 +24,6 @@ from jedi.evaluate.cache import memoize_default from jedi.evaluate import imports from jedi.evaluate.param import TreeArguments, create_default_param from jedi.common import to_list, unite -from jedi.evaluate import context MAX_PARAM_SEARCHES = 20 @@ -103,33 +100,21 @@ def _search_function_executions(evaluator, module_context, funcdef): """ from jedi.evaluate import representation as er - def get_possible_nodes(module_context, func_name): - if not isinstance(module_context, er.ModuleContext): - return - try: - names = module_context.module_node.used_names[func_name] - except KeyError: - return - - for name in names: - bracket = name.get_next_leaf() - trailer = bracket.parent - if trailer.type == 'trailer' and bracket == '(': - yield name, trailer - - func_name = unicode(funcdef.name) + func_string_name = funcdef.name.value compare_node = funcdef - if func_name == '__init__': + if func_string_name == '__init__': cls = funcdef.get_parent_scope() if isinstance(cls, tree.Class): - func_name = unicode(cls.name) + func_string_name = cls.name.value compare_node = cls found_executions = False i = 0 for for_mod_context in imports.get_modules_containing_name( - evaluator, [module_context], func_name): - for name, trailer in get_possible_nodes(for_mod_context, func_name): + evaluator, [module_context], func_string_name): + if not isinstance(module_context, er.ModuleContext): + return + for name, trailer in _get_possible_nodes(for_mod_context, func_string_name): i += 1 # This is a simple way to stop Jedi's dynamic param recursion @@ -138,23 +123,79 @@ def _search_function_executions(evaluator, module_context, funcdef): if i * evaluator.dynamic_params_depth > MAX_PARAM_SEARCHES: return - random_context = evaluator.create_context(module_context, name) - for value in evaluator.goto_definitions(random_context, name): - value_node = value.get_node() - if compare_node == value_node: - arglist = trailer.children[1] - if arglist == ')': - arglist = () - args = TreeArguments(evaluator, random_context, arglist, trailer) - yield er.FunctionExecutionContext( - evaluator, - value.parent_context, - value_node, - args - ) - found_executions = True + random_context = evaluator.create_context(for_mod_context, name) + for function_execution in _check_name_for_execution( + evaluator, random_context, compare_node, name, trailer): + found_executions = True + yield function_execution # If there are results after processing a module, we're probably # good to process. This is a speed optimization. if found_executions: return + + +def _get_possible_nodes(module_context, func_string_name): + try: + names = module_context.module_node.used_names[func_string_name] + except KeyError: + return + + for name in names: + bracket = name.get_next_leaf() + trailer = bracket.parent + if trailer.type == 'trailer' and bracket == '(': + yield name, trailer + + +def _check_name_for_execution(evaluator, context, compare_node, name, trailer): + from jedi.evaluate import representation as er, instance + + def create_func_excs(): + arglist = trailer.children[1] + if arglist == ')': + arglist = () + args = TreeArguments(evaluator, context, arglist, trailer) + if value_node.type == 'funcdef': + yield value.get_function_execution(args) + else: + created_instance = instance.TreeInstance( + evaluator, + value.parent_context, + value, + args + ) + for execution in created_instance.create_init_executions(): + yield execution + + for value in evaluator.goto_definitions(context, name): + value_node = value.get_node() + if compare_node == value_node: + for func_execution in create_func_excs(): + yield func_execution + elif isinstance(value.parent_context, er.FunctionExecutionContext) and \ + compare_node.type == 'funcdef': + # Here we're trying to find decorators by checking the first + # parameter. It's not very generic though. Should find a better + # solution that also applies to nested decorators. + params = value.parent_context.get_params() + if len(params) != 1: + continue + values = params[0].infer() + nodes = [value.get_node() for value in values] + if nodes == [compare_node]: + # Found a decorator. + module_context = context.get_root_context() + execution_context = next(create_func_excs()) + for name, trailer in _get_possible_nodes(module_context, params[0].string_name): + if value_node.start_pos < name.start_pos < value_node.end_pos: + random_context = evaluator.create_context(execution_context, name) + iterator = _check_name_for_execution( + evaluator, + random_context, + compare_node, + name, + trailer + ) + for function_execution in iterator: + yield function_execution diff --git a/jedi/evaluate/helpers.py b/jedi/evaluate/helpers.py index 8694d586..db644923 100644 --- a/jedi/evaluate/helpers.py +++ b/jedi/evaluate/helpers.py @@ -2,6 +2,7 @@ import copy from itertools import chain from jedi.parser import tree +from jedi.common import unite def deep_ast_copy(obj, parent=None, new_elements=None): @@ -64,11 +65,10 @@ def deep_ast_copy(obj, parent=None, new_elements=None): if parent is not None: new_obj.parent = parent - raise NotImplementedError return new_obj -def call_of_leaf(leaf, cut_own_trailer=False): +def evaluate_call_of_leaf(context, leaf, cut_own_trailer=False): """ Creates a "call" node that consist of all ``trailer`` and ``power`` objects. E.g. if you call it with ``append``:: @@ -90,8 +90,8 @@ def call_of_leaf(leaf, cut_own_trailer=False): # we should not match anything more than x. if trailer.type != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]): if trailer.type == 'atom': - return trailer - return leaf + return context.eval_node(trailer) + return context.eval_node(leaf) power = trailer.parent index = power.children.index(trailer) @@ -100,21 +100,25 @@ def call_of_leaf(leaf, cut_own_trailer=False): else: cut = index + 1 - new_power = copy.copy(power) - new_power.children = list(new_power.children) - new_power.children[cut:] = [] + values = context.eval_node(power.children[0]) + for trailer in power.children[1:cut]: + values = context.eval_trailer(values, trailer) + return values - if power.type == 'error_node': + # TODO delete + ''' + if new_power.type == 'error_node': start = index while True: start -= 1 - if power.children[start].type != 'trailer': + if new_power.children[start].type != 'trailer': break - transformed = tree.Node('power', power.children[start:]) - transformed.parent = power.parent + transformed = tree.Node('power', new_power.children[start:]) + transformed.parent = new_power.parent return transformed + ''' - return power + return new_power def get_names_of_node(node): diff --git a/jedi/evaluate/instance.py b/jedi/evaluate/instance.py index a9dbebe4..62ecf31e 100644 --- a/jedi/evaluate/instance.py +++ b/jedi/evaluate/instance.py @@ -142,6 +142,19 @@ class AbstractInstanceContext(Context): def name(self): pass + def _create_init_execution(self, class_context, func_node): + return InstanceFunctionExecution( + self, + class_context.parent_context, + func_node, + self.var_args + ) + + def create_init_executions(self): + for name in self.get_function_slot_names('__init__'): + if isinstance(name, LazyInstanceName): + yield self._create_init_execution(name.class_context, name.tree_name.parent) + @memoize_default() def create_instance_context(self, class_context, node): if node.parent.type in ('funcdef', 'classdef'): @@ -153,12 +166,7 @@ class AbstractInstanceContext(Context): parent_context = self.create_instance_context(class_context, scope) if scope.type == 'funcdef': if scope.name.value == '__init__' and parent_context == class_context: - return InstanceFunctionExecution( - self, - class_context.parent_context, - scope, - self.var_args - ) + return self._create_init_execution(class_context, scope) else: return AnonymousInstanceFunctionExecution( self, @@ -230,7 +238,7 @@ class CompiledInstanceClassFilter(compiled.CompiledObjectFilter): return self.name_class(self._evaluator, self._instance, self._compiled_obj, name) -class BoundMethod(Context): +class BoundMethod(object): def __init__(self, instance, class_context, function): self._instance = instance self._class_context = class_context @@ -239,13 +247,16 @@ class BoundMethod(Context): def __getattr__(self, name): return getattr(self._function, name) - def py__call__(self, var_args): - function_execution = InstanceFunctionExecution( + def get_function_execution(self, arguments): + return InstanceFunctionExecution( self._instance, self.parent_context, self._function.funcdef, - var_args + arguments ) + + def py__call__(self, arguments): + function_execution = self.get_function_execution(arguments) return self._function.infer_function_execution(function_execution) def __repr__(self): @@ -265,19 +276,19 @@ class LazyInstanceName(filters.TreeNameDefinition): """ def __init__(self, instance, class_context, tree_name): self._instance = instance - self._class_context = class_context + 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) + return self._instance.create_instance_context(self.class_context, self.tree_name) class LazyInstanceClassName(LazyInstanceName): def infer(self): for v in super(LazyInstanceClassName, self).infer(): if isinstance(v, er.FunctionContext): - yield BoundMethod(self._instance, self._class_context, v) + yield BoundMethod(self._instance, self.class_context, v) else: yield v diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index 9c679e49..8e4b19dc 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -209,7 +209,7 @@ class Comprehension(AbstractSequence): # InstanceElement anyway, I don't care. node = node.var last_comp = list(comp_for.get_comp_fors())[-1] - raise NotImplementedError('should not need to copy...') + #TODO raise NotImplementedError('should not need to copy...') return helpers.deep_ast_copy(node, parent=last_comp) def _nested(self, comp_fors): diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index fa64f3a2..f23e46f2 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -78,6 +78,7 @@ class Instance(use_metaclass(CachedMetaClass, Executed)): This class is used to evaluate instances. """ def __init__(self, evaluator, parent_context, class_context, var_args, is_generated=False): + raise DeprecationWarning super(Instance, self).__init__(evaluator, parent_context, var_args) # Generated instances are classes that are just generated by self # (No var_args) used. @@ -562,14 +563,17 @@ class FunctionContext(use_metaclass(CachedMetaClass, context.TreeContext, Wrappe else: return function_execution.get_return_values() - @Python3Method - def py__call__(self, params): - function_execution = FunctionExecutionContext( + def get_function_execution(self, arguments): + return FunctionExecutionContext( self.evaluator, self.parent_context, self.base, - params + arguments ) + + @Python3Method + def py__call__(self, arguments): + function_execution = self.get_function_execution(arguments) return self.infer_function_execution(function_execution) def py__class__(self): diff --git a/test/completion/dynamic_params.py b/test/completion/dynamic_params.py index 2afc9863..f9de0286 100644 --- a/test/completion/dynamic_params.py +++ b/test/completion/dynamic_params.py @@ -56,11 +56,11 @@ def func(c): return c #? str() -func("str") +func("something") @def_func def func(c=1): - #? int() float() + #? float() return c func(1.0)