1
0
forked from VimPlug/jedi

Fix dynamic param tests.

This commit is contained in:
Dave Halter
2016-11-24 00:11:26 +01:00
parent 06efc8fb8c
commit 7ed1c95737
10 changed files with 149 additions and 88 deletions

View File

@@ -7,7 +7,7 @@ from jedi.api import helpers
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.api import keywords from jedi.api import keywords
from jedi.evaluate import compiled 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 from jedi.evaluate.filters import get_global_filters
@@ -164,8 +164,7 @@ class Completion:
return list(self._get_class_context_completions(is_function=True)) return list(self._get_class_context_completions(is_function=True))
elif symbol_names[-1] == 'trailer' and nodes[-1] == '.': elif symbol_names[-1] == 'trailer' and nodes[-1] == '.':
dot = self._module_node.get_leaf_for_position(self._position) dot = self._module_node.get_leaf_for_position(self._position)
atom_expr = call_of_leaf(dot.get_previous_leaf()) completion_names += self._trailer_completions(dot.get_previous_leaf())
completion_names += self._trailer_completions(atom_expr)
else: else:
completion_names += self._global_completions() completion_names += self._global_completions()
completion_names += self._get_class_context_completions(is_function=False) completion_names += self._get_class_context_completions(is_function=False)
@@ -194,10 +193,12 @@ class Completion:
completion_names += filter.values() completion_names += filter.values()
return completion_names 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) user_context = get_user_scope(self._module_context, self._position)
evaluation_context = self._evaluator.create_context(self._module_context, atom_expr) evaluation_context = self._evaluator.create_context(
contexts = self._evaluator.eval_element(evaluation_context, atom_expr) self._module_context, previous_leaf
)
contexts = evaluate_call_of_leaf(evaluation_context, previous_leaf)
completion_names = [] completion_names = []
debug.dbg('trailer completion contexts: %s', contexts) debug.dbg('trailer completion contexts: %s', contexts)
for context in contexts: for context in contexts:

View File

@@ -5,7 +5,7 @@ import re
from collections import namedtuple from collections import namedtuple
from jedi._compatibility import u 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 import parser
from jedi.parser import tokenize from jedi.parser import tokenize
from jedi.cache import time_cache from jedi.cache import time_cache
@@ -198,16 +198,12 @@ def evaluate_goto_definition(evaluator, context, leaf):
# magic itself. # magic itself.
return evaluator.goto_definitions(context, leaf) return evaluator.goto_definitions(context, leaf)
node = None
parent = leaf.parent parent = leaf.parent
if parent.type == 'atom': if parent.type == 'atom':
node = leaf.parent return context.eval_node(leaf.parent)
elif parent.type == 'trailer': elif parent.type == 'trailer':
node = call_of_leaf(leaf) return evaluate_call_of_leaf(context, leaf)
return []
if node is None:
return []
return evaluator.eval_element(context, node)
CallSignatureDetails = namedtuple( CallSignatureDetails = namedtuple(

View File

@@ -429,8 +429,7 @@ class Evaluator(object):
elif def_.type in ('import_from', 'import_name'): elif def_.type in ('import_from', 'import_name'):
return imports.ImportWrapper(context, name).follow() return imports.ImportWrapper(context, name).follow()
call = helpers.call_of_leaf(name) return helpers.evaluate_call_of_leaf(context, name)
return self.eval_element(context, call)
def goto(self, context, name): def goto(self, context, name):
def resolve_implicit_imports(names): def resolve_implicit_imports(names):
@@ -522,7 +521,7 @@ class Evaluator(object):
raise DeprecationWarning raise DeprecationWarning
return element 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): def from_scope_node(scope_node, child_is_funcdef=None):
is_funcdef = scope_node.type == 'funcdef' is_funcdef = scope_node.type == 'funcdef'
parent_context = None parent_context = None
@@ -531,8 +530,8 @@ class Evaluator(object):
parent_context = from_scope_node(parent_scope, child_is_funcdef=is_funcdef) parent_context = from_scope_node(parent_scope, child_is_funcdef=is_funcdef)
# TODO this whole procedure just ignores decorators # TODO this whole procedure just ignores decorators
if scope_node == module_context.module_node: if scope_node == base_node:
return module_context return base_context
elif is_funcdef: elif is_funcdef:
if isinstance(parent_context, AnonymousInstance): if isinstance(parent_context, AnonymousInstance):
return AnonymousInstanceFunctionExecution( return AnonymousInstanceFunctionExecution(
@@ -550,6 +549,8 @@ class Evaluator(object):
else: else:
return class_context return class_context
base_node = base_context.get_node()
if node.is_scope(): if node.is_scope():
scope_node = node scope_node = node
else: else:

View File

@@ -43,6 +43,9 @@ class Context(object):
def eval_stmt(self, stmt, seek_name=None): def eval_stmt(self, stmt, seek_name=None):
return self.evaluator.eval_statement(self, stmt, seek_name) 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): class TreeContext(Context):
pass pass

View File

@@ -16,9 +16,7 @@ It works as follows:
- search for function calls named ``foo`` - search for function calls named ``foo``
- execute these calls and check the input. This work with a ``ParamListener``. - 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.parser import tree
from jedi import settings from jedi import settings
from jedi import debug from jedi import debug
@@ -26,7 +24,6 @@ from jedi.evaluate.cache import memoize_default
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate.param import TreeArguments, create_default_param from jedi.evaluate.param import TreeArguments, create_default_param
from jedi.common import to_list, unite from jedi.common import to_list, unite
from jedi.evaluate import context
MAX_PARAM_SEARCHES = 20 MAX_PARAM_SEARCHES = 20
@@ -103,33 +100,21 @@ def _search_function_executions(evaluator, module_context, funcdef):
""" """
from jedi.evaluate import representation as er from jedi.evaluate import representation as er
def get_possible_nodes(module_context, func_name): func_string_name = funcdef.name.value
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)
compare_node = funcdef compare_node = funcdef
if func_name == '__init__': if func_string_name == '__init__':
cls = funcdef.get_parent_scope() cls = funcdef.get_parent_scope()
if isinstance(cls, tree.Class): if isinstance(cls, tree.Class):
func_name = unicode(cls.name) func_string_name = cls.name.value
compare_node = cls compare_node = cls
found_executions = False found_executions = False
i = 0 i = 0
for for_mod_context in imports.get_modules_containing_name( for for_mod_context in imports.get_modules_containing_name(
evaluator, [module_context], func_name): evaluator, [module_context], func_string_name):
for name, trailer in get_possible_nodes(for_mod_context, func_name): if not isinstance(module_context, er.ModuleContext):
return
for name, trailer in _get_possible_nodes(for_mod_context, func_string_name):
i += 1 i += 1
# This is a simple way to stop Jedi's dynamic param recursion # 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: if i * evaluator.dynamic_params_depth > MAX_PARAM_SEARCHES:
return return
random_context = evaluator.create_context(module_context, name) random_context = evaluator.create_context(for_mod_context, name)
for value in evaluator.goto_definitions(random_context, name): for function_execution in _check_name_for_execution(
value_node = value.get_node() evaluator, random_context, compare_node, name, trailer):
if compare_node == value_node: found_executions = True
arglist = trailer.children[1] yield function_execution
if arglist == ')':
arglist = ()
args = TreeArguments(evaluator, random_context, arglist, trailer)
yield er.FunctionExecutionContext(
evaluator,
value.parent_context,
value_node,
args
)
found_executions = True
# If there are results after processing a module, we're probably # If there are results after processing a module, we're probably
# good to process. This is a speed optimization. # good to process. This is a speed optimization.
if found_executions: if found_executions:
return 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

View File

@@ -2,6 +2,7 @@ import copy
from itertools import chain from itertools import chain
from jedi.parser import tree from jedi.parser import tree
from jedi.common import unite
def deep_ast_copy(obj, parent=None, new_elements=None): 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: if parent is not None:
new_obj.parent = parent new_obj.parent = parent
raise NotImplementedError
return new_obj 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`` Creates a "call" node that consist of all ``trailer`` and ``power``
objects. E.g. if you call it with ``append``:: 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. # 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 != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]):
if trailer.type == 'atom': if trailer.type == 'atom':
return trailer return context.eval_node(trailer)
return leaf return context.eval_node(leaf)
power = trailer.parent power = trailer.parent
index = power.children.index(trailer) index = power.children.index(trailer)
@@ -100,21 +100,25 @@ def call_of_leaf(leaf, cut_own_trailer=False):
else: else:
cut = index + 1 cut = index + 1
new_power = copy.copy(power) values = context.eval_node(power.children[0])
new_power.children = list(new_power.children) for trailer in power.children[1:cut]:
new_power.children[cut:] = [] values = context.eval_trailer(values, trailer)
return values
if power.type == 'error_node': # TODO delete
'''
if new_power.type == 'error_node':
start = index start = index
while True: while True:
start -= 1 start -= 1
if power.children[start].type != 'trailer': if new_power.children[start].type != 'trailer':
break break
transformed = tree.Node('power', power.children[start:]) transformed = tree.Node('power', new_power.children[start:])
transformed.parent = power.parent transformed.parent = new_power.parent
return transformed return transformed
'''
return power return new_power
def get_names_of_node(node): def get_names_of_node(node):

View File

@@ -142,6 +142,19 @@ class AbstractInstanceContext(Context):
def name(self): def name(self):
pass 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() @memoize_default()
def create_instance_context(self, class_context, node): def create_instance_context(self, class_context, node):
if node.parent.type in ('funcdef', 'classdef'): if node.parent.type in ('funcdef', 'classdef'):
@@ -153,12 +166,7 @@ class AbstractInstanceContext(Context):
parent_context = self.create_instance_context(class_context, scope) parent_context = self.create_instance_context(class_context, scope)
if scope.type == 'funcdef': if scope.type == 'funcdef':
if scope.name.value == '__init__' and parent_context == class_context: if scope.name.value == '__init__' and parent_context == class_context:
return InstanceFunctionExecution( return self._create_init_execution(class_context, scope)
self,
class_context.parent_context,
scope,
self.var_args
)
else: else:
return AnonymousInstanceFunctionExecution( return AnonymousInstanceFunctionExecution(
self, self,
@@ -230,7 +238,7 @@ class CompiledInstanceClassFilter(compiled.CompiledObjectFilter):
return self.name_class(self._evaluator, self._instance, self._compiled_obj, name) 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): def __init__(self, instance, class_context, function):
self._instance = instance self._instance = instance
self._class_context = class_context self._class_context = class_context
@@ -239,13 +247,16 @@ class BoundMethod(Context):
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self._function, name) return getattr(self._function, name)
def py__call__(self, var_args): def get_function_execution(self, arguments):
function_execution = InstanceFunctionExecution( return InstanceFunctionExecution(
self._instance, self._instance,
self.parent_context, self.parent_context,
self._function.funcdef, 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) return self._function.infer_function_execution(function_execution)
def __repr__(self): def __repr__(self):
@@ -265,19 +276,19 @@ class LazyInstanceName(filters.TreeNameDefinition):
""" """
def __init__(self, instance, class_context, tree_name): def __init__(self, instance, class_context, tree_name):
self._instance = instance self._instance = instance
self._class_context = class_context self.class_context = class_context
self.tree_name = tree_name self.tree_name = tree_name
@property @property
def parent_context(self): 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): class LazyInstanceClassName(LazyInstanceName):
def infer(self): def infer(self):
for v in super(LazyInstanceClassName, self).infer(): for v in super(LazyInstanceClassName, self).infer():
if isinstance(v, er.FunctionContext): if isinstance(v, er.FunctionContext):
yield BoundMethod(self._instance, self._class_context, v) yield BoundMethod(self._instance, self.class_context, v)
else: else:
yield v yield v

View File

@@ -209,7 +209,7 @@ class Comprehension(AbstractSequence):
# InstanceElement anyway, I don't care. # InstanceElement anyway, I don't care.
node = node.var node = node.var
last_comp = list(comp_for.get_comp_fors())[-1] 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) return helpers.deep_ast_copy(node, parent=last_comp)
def _nested(self, comp_fors): def _nested(self, comp_fors):

View File

@@ -78,6 +78,7 @@ class Instance(use_metaclass(CachedMetaClass, Executed)):
This class is used to evaluate instances. This class is used to evaluate instances.
""" """
def __init__(self, evaluator, parent_context, class_context, var_args, is_generated=False): def __init__(self, evaluator, parent_context, class_context, var_args, is_generated=False):
raise DeprecationWarning
super(Instance, self).__init__(evaluator, parent_context, var_args) super(Instance, self).__init__(evaluator, parent_context, var_args)
# Generated instances are classes that are just generated by self # Generated instances are classes that are just generated by self
# (No var_args) used. # (No var_args) used.
@@ -562,14 +563,17 @@ class FunctionContext(use_metaclass(CachedMetaClass, context.TreeContext, Wrappe
else: else:
return function_execution.get_return_values() return function_execution.get_return_values()
@Python3Method def get_function_execution(self, arguments):
def py__call__(self, params): return FunctionExecutionContext(
function_execution = FunctionExecutionContext(
self.evaluator, self.evaluator,
self.parent_context, self.parent_context,
self.base, self.base,
params arguments
) )
@Python3Method
def py__call__(self, arguments):
function_execution = self.get_function_execution(arguments)
return self.infer_function_execution(function_execution) return self.infer_function_execution(function_execution)
def py__class__(self): def py__class__(self):

View File

@@ -56,11 +56,11 @@ def func(c):
return c return c
#? str() #? str()
func("str") func("something")
@def_func @def_func
def func(c=1): def func(c=1):
#? int() float() #? float()
return c return c
func(1.0) func(1.0)