A lot of call signature refactorings. Note that this commit is totally broken.

This commit is contained in:
Dave Halter
2016-06-17 00:20:13 +02:00
parent 6f366e2d77
commit 32346c6da8
6 changed files with 107 additions and 85 deletions

View File

@@ -182,8 +182,12 @@ class Script(object):
:rtype: list of :class:`classes.Definition` :rtype: list of :class:`classes.Definition`
""" """
c = helpers.ContextResults(self._evaluator, self.source, self._get_module(), self._pos) leaf = self._get_module().name_for_position(self._pos)
definitions = c.get_results() if leaf is None:
leaf = self._get_module().get_leaf_for_position(self._pos)
if leaf is None:
return []
definitions = helpers.evaluate_goto_definition(self._evaluator, leaf)
names = [s.name for s in definitions] names = [s.name for s in definitions]
defs = [classes.Definition(self._evaluator, name) for name in names] defs = [classes.Definition(self._evaluator, name) for name in names]
@@ -272,26 +276,30 @@ class Script(object):
abs()# <-- cursor is here abs()# <-- cursor is here
This would return ``None``. This would return an empty list..
:rtype: list of :class:`classes.CallSignature` :rtype: list of :class:`classes.CallSignature`
""" """
call_txt, call_index, key_name, start_pos = self._user_context.call_signature() call_signature_details = \
if call_txt is None: helpers.get_call_signature_details(self._get_module(), self._pos)
if call_signature_details is None:
return [] return []
stmt = inference.get_under_cursor_stmt(self._evaluator, self._parser, # TODO insert caching again here.
call_txt, start_pos) #with common.scale_speed_settings(settings.scale_call_signatures):
if stmt is None: # definitions = cache.cache_call_signatures(self._evaluator, stmt,
return [] # self.source, self._pos)
definitions = helpers.evaluate_goto_definition(
with common.scale_speed_settings(settings.scale_call_signatures): self._evaluator,
origins = cache.cache_call_signatures(self._evaluator, stmt, call_signature_details.leaf
self.source, self._pos) )
debug.speed('func_call followed') debug.speed('func_call followed')
return [classes.CallSignature(self._evaluator, o.name, stmt, call_index, key_name) return [classes.CallSignature(self._evaluator, d.name,
for o in origins if hasattr(o, 'py__call__')] call_signature_details.leaf.start_pos,
call_signature_details.call_index,
call_signature_details.keyword_name)
for d in definitions if hasattr(d, 'py__call__')]
def _analysis(self): def _analysis(self):
self._evaluator.is_analysis = True self._evaluator.is_analysis = True

View File

@@ -629,11 +629,11 @@ class CallSignature(Definition):
It knows what functions you are currently in. e.g. `isinstance(` would It knows what functions you are currently in. e.g. `isinstance(` would
return the `isinstance` function. without `(` it would return nothing. return the `isinstance` function. without `(` it would return nothing.
""" """
def __init__(self, evaluator, executable_name, call_stmt, index, key_name): def __init__(self, evaluator, executable_name, bracket_start_pos, index, key_name):
super(CallSignature, self).__init__(evaluator, executable_name) super(CallSignature, self).__init__(evaluator, executable_name)
self._index = index self._index = index
self._key_name = key_name self._key_name = key_name
self._call_stmt = call_stmt self._bracket_start_pos = bracket_start_pos
@property @property
def index(self): def index(self):
@@ -665,7 +665,7 @@ class CallSignature(Definition):
The indent of the bracket that is responsible for the last function The indent of the bracket that is responsible for the last function
call. call.
""" """
return self._call_stmt.end_pos return self._bracket_start_pos
@property @property
def call_name(self): def call_name(self):

View File

@@ -6,6 +6,7 @@ from collections import namedtuple
from jedi import common from jedi import common
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate.helpers import deep_ast_copy
from jedi import parser from jedi import parser
from jedi.parser import tokenize, token from jedi.parser import tokenize, token
@@ -169,68 +170,63 @@ def get_possible_completion_types(grammar, stack):
return keywords, grammar_labels return keywords, grammar_labels
class ContextResults(): def evaluate_goto_definition(evaluator, leaf):
def __init__(self, evaluator, source, module, pos): if leaf.type == 'name':
self._evaluator = evaluator # In case of a name we can just use goto_definition which does all the
self._module = module # magic itself.
self._source = source return evaluator.goto_definitions(leaf)
self._pos = pos
def _on_defining_name(self, leaf): node = None
return [self._evaluator.wrap(self._parser.user_scope())] parent = leaf.parent
if parent.type == 'atom':
node = leaf.parent
elif parent.type == 'trailer':
index = parent.parent.children.index(parent)
node = deep_ast_copy(parent.parent)
node.children = node.children[:index + 1]
def get_results(self): if node is None:
'''
try:
stack = get_stack_at_position(self._evaluator.grammar, self._source, self._module, self._leaf.end_pos)
except OnErrorLeaf:
return []
'''
name = self._module.name_for_position(self._pos)
if name is not None:
return self._evaluator.goto_definitions(name)
leaf = self._module.get_leaf_for_position(self._pos)
if leaf is None:
return []
if leaf.parent.type == 'atom':
return self._evaluator.eval_element(leaf.parent)
if leaf.parent.type == 'trailer':
return self._evaluator.eval_element(leaf.parent.parent)
return [] return []
symbol_names = list(stack.get_node_names(self._evaluator.grammar)) return evaluator.eval_element(node)
nodes = list(stack.get_nodes())
if "import_stmt" in symbol_names:
level = 0
only_modules = True
level, names = self._parse_dotted_names(nodes)
if "import_from" in symbol_names:
if 'import' in nodes:
only_modules = False
else:
assert "import_name" in symbol_names
completion_names += self._get_importer_names(
names,
level,
only_modules
)
elif nodes[-2] in ('as', 'def', 'class'):
# No completions for ``with x as foo`` and ``import x as foo``.
# Also true for defining names as a class or function.
return self._on_defining_name(self._leaf)
else:
completion_names += self._simple_complete(completion_parts)
return
class GotoDefinition(ContextResults): CallSignatureDetails = namedtuple(
def _(): 'CallSignatureDetails',
definitions = inference.type_inference( ['leaf', 'call_index', 'keyword_name']
self._evaluator, self._parser, self._user_context, )
self._pos, goto_path
)
def _get_call_signature_details_from_error_node(node, position):
for index, element in reversed(list(enumerate(node.children))):
# `index > 0` means that it's a trailer and not an atom.
if element == '(' and element.end_pos <= position and index > 0:
name = element.get_previous_leaf()
if name.type == 'name':
nodes_before = [c for c in node.children[index:] if c.start_pos < position]
return CallSignatureDetails(name, nodes_before.count(','), None)
def get_call_signature_details(module, position):
leaf = module.get_leaf_for_position(position, include_prefixes=True)
if leaf == ')':
if leaf.end_pos == position:
leaf = leaf.get_next_leaf()
# Now that we know where we are in the syntax tree, we start to look at
# parents for possible function definitions.
node = leaf.parent
name = None
while node is not None:
for n in node.children:
if n.start_pos < position and n.type == 'error_node':
result = _get_call_signature_details_from_error_node(n, position)
if result is not None:
return result
if node.type == 'trailer' and node.children[0] == '(':
name = node.get_previous_sibling()
nodes_before = [c for c in node.children if c.start_pos < position]
return CallSignatureDetails(name, nodes_before.count(','), None)
node = node.parent
return None

View File

@@ -81,15 +81,32 @@ def call_of_name(name, cut_own_trailer=False):
# TODO remove cut_own_trailer option, since its always used with it. Just # TODO remove cut_own_trailer option, since its always used with it. Just
# ignore it, It's not what we want anyway. Or document it better? # ignore it, It's not what we want anyway. Or document it better?
""" """
par = name trailer = name.parent
if tree.is_node(par.parent, 'trailer'): if trailer.type != 'trailer' or trailer.children[0] != '.':
return name
assert not cut_own_trailer # TODO remove
power = trailer.parent
index = power.children.index(trailer)
power = deep_ast_copy(power)
power.children[index + 1:] = []
if power.type == 'error_node':
transformed = tree.Node('power', power.children)
transformed.parent = power.parent
return transformed
return power
if 1:
par = par.parent par = par.parent
if par.children[0] in ('(', '['): if par.children[0] in ('(', '['):
# The trailer is not a NAME.NAME trailer, but a call to something. # The trailer is not a NAME.NAME trailer, but a call to something.
return name return name
power = par.parent power = par.parent
if tree.is_node(power, 'power', 'atom_expr') \ # `atom_expr` got introduced in Python 3.5 and is essentially just the
# whole call part without the optional ** power element.
if power.type in ('power', 'atom_expr') \
and power.children[0] != name \ and power.children[0] != name \
and not (power.children[-2] == '**' and and not (power.children[-2] == '**' and
name.start_pos > power.children[-1].start_pos): name.start_pos > power.children[-1].start_pos):

View File

@@ -83,6 +83,9 @@ TestClass.var_local.
#? int() #? int()
TestClass().ret(1) TestClass().ret(1)
# Should not return int(), because we want the type before `.ret(1)`.
#? 11 TestClass()
TestClass().ret(1)
#? int() #? int()
inst.ret(1) inst.ret(1)

View File

@@ -14,7 +14,8 @@ class TestCallSignatures(TestCase):
assert len(signatures) <= 1 assert len(signatures) <= 1
if not signatures: if not signatures:
assert expected_name is None assert expected_name is None, \
'There are no signatures, but %s expected.' % expected_name
else: else:
assert signatures[0].name == expected_name assert signatures[0].name == expected_name
assert signatures[0].index == expected_index assert signatures[0].index == expected_index
@@ -27,9 +28,6 @@ class TestCallSignatures(TestCase):
def test_simple(self): def test_simple(self):
run = self._run_simple run = self._run_simple
s7 = "str().upper().center("
s8 = "str(int[zip("
run(s7, 'center', 0)
# simple # simple
s1 = "sorted(a, str(" s1 = "sorted(a, str("