Fix CallSignature index for a looot of cases, fixes #1364,#1363

This commit is contained in:
Dave Halter
2019-07-08 12:40:58 -07:00
parent 943617a94f
commit 08019075c3
3 changed files with 150 additions and 51 deletions

View File

@@ -615,6 +615,7 @@ class CallSignature(Definition):
The Param index of the current call. The Param index of the current call.
Returns None if the index cannot be found in the curent call. Returns None if the index cannot be found in the curent call.
""" """
return self._call_details.calculate_index(self._signature.get_param_names())
if self._call_details.keyword_name_str is not None: if self._call_details.keyword_name_str is not None:
for i, param in enumerate(self.params): for i, param in enumerate(self.params):
if self._call_details.keyword_name_str == param.name: if self._call_details.keyword_name_str == param.name:

View File

@@ -8,7 +8,7 @@ from textwrap import dedent
from parso.python.parser import Parser from parso.python.parser import Parser
from parso.python import tree from parso.python import tree
from jedi._compatibility import u from jedi._compatibility import u, Parameter
from jedi.evaluate.base_context import NO_CONTEXTS from jedi.evaluate.base_context import NO_CONTEXTS
from jedi.evaluate.syntax_tree import eval_atom from jedi.evaluate.syntax_tree import eval_atom
from jedi.evaluate.helpers import evaluate_call_of_leaf from jedi.evaluate.helpers import evaluate_call_of_leaf
@@ -174,6 +174,104 @@ class CallDetails(object):
def keyword_name_str(self): def keyword_name_str(self):
return _get_index_and_key(self._children, self._position)[1] return _get_index_and_key(self._children, self._position)[1]
def calculate_index(self, param_names):
positional_count = 0
used_names = set()
star_count = -1
args = list(_iter_arguments(self._children, self._position))
if not args:
if param_names:
return 0
else:
return None
is_kwarg = False
for i, (star_count, key_start, had_equal) in enumerate(args):
is_kwarg |= had_equal | (star_count == 2)
if star_count:
pass # For now do nothing, we don't know what's in there here.
elif had_equal:
if i + 1 != len(args):
used_names.add(key_start)
else:
positional_count += 1
for i, param_name in enumerate(param_names):
kind = param_name.get_kind()
if not is_kwarg:
if kind == Parameter.VAR_POSITIONAL:
return i
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
if i + 1 == positional_count:
return i
if key_start is not None:
if param_name.string_name not in used_names \
and (kind == Parameter.KEYWORD_ONLY
or kind == Parameter.POSITIONAL_OR_KEYWORD
and positional_count < i + 1):
if had_equal:
if param_name.string_name == key_start:
return i
else:
if param_name.string_name.startswith(key_start):
return i
if kind == Parameter.VAR_KEYWORD:
return i
return None
def _iter_arguments(nodes, position):
def remove_after_pos(name):
return name.value[:position[1] - name.start_pos[1]]
# Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
nodes_before = [c for c in nodes if c.start_pos < position]
if nodes_before[-1].type == 'arglist':
for x in _iter_arguments(nodes_before[-1].children, position):
yield x # Python 2 :(
return
previous_node_yielded = False
for i, node in enumerate(nodes_before):
if node.type == 'argument':
previous_node_yielded = True
first = node.children[0]
second = node.children[1]
if second == '=':
if second.start_pos < position:
yield 0, first.value, True
else:
yield 0, remove_after_pos(first), True
elif first in ('*', '**'):
yield len(first), remove_after_pos(second), ''
else:
# Must be a Comprehension
first_leaf = node.get_first_leaf()
if first_leaf.type == 'name' and first_leaf.start_pos >= position:
yield 0, remove_after_pos(first_leaf), False
else:
yield 0, None, False
elif node == ',':
if not previous_node_yielded:
yield 0, '', False
previous_node_yielded = False
elif node == '=' and nodes_before[-1]:
previous_node_yielded = True
before = nodes_before[i - 1]
if before.type == 'name':
yield 0, before.value, True
else:
yield 0, None, False
if not previous_node_yielded:
if nodes_before[-1].type == 'name':
yield 0, remove_after_pos(nodes_before[-1]), False
else:
yield 0, '', False
def _get_index_and_key(nodes, position): def _get_index_and_key(nodes, position):
""" """
@@ -185,7 +283,6 @@ def _get_index_and_key(nodes, position):
key_str = None key_str = None
if nodes_before:
last = nodes_before[-1] last = nodes_before[-1]
if last.type == 'argument' and last.children[1] == '=' \ if last.type == 'argument' and last.children[1] == '=' \
and last.children[1].end_pos <= position: and last.children[1].end_pos <= position:

View File

@@ -396,13 +396,13 @@ def test_keyword_argument_index(Script, environment):
code1 = 'def f(u, /, v=3, *, abc, abd, xyz): pass' code1 = 'def f(u, /, v=3, *, abc, abd, xyz): pass'
code2 = 'def f(u, /, v=3, *, abc, abd, xyz, **kwargs): pass' code2 = 'def g(u, /, v=3, *, abc, abd, xyz, **kwargs): pass'
code3 = 'def f(u, /, v, *args, x=1, y): pass' code3 = 'def h(u, /, v, *args, x=1, y): pass'
code4 = 'def f(u, /, v, *args, x=1, y, **kwargs): pass' code4 = 'def i(u, /, v, *args, x=1, y, **kwargs): pass'
@pytest.mark.parametrize( @pytest.mark.parametrize(
'code, call, index', [ 'code, call, expected_index', [
# No *args, **kwargs # No *args, **kwargs
(code1, 'f(', 0), (code1, 'f(', 0),
(code1, 'f(a', 0), (code1, 'f(a', 0),
@@ -424,51 +424,52 @@ code4 = 'def f(u, /, v, *args, x=1, y, **kwargs): pass'
(code1, 'f(v=', 1), (code1, 'f(v=', 1),
# **kwargs # **kwargs
(code2, 'f(a,b,a', 2), (code2, 'g(a,b,a', 2),
(code2, 'f(a,b,abd', 2), (code2, 'g(a,b,abc', 2),
(code2, 'f(a,b,arr', 5), (code2, 'g(a,b,abd', 3),
(code2, 'f(a,b,xy', 4), (code2, 'g(a,b,arr', 5),
(code2, 'f(a,b,xy=', 4), (code2, 'g(a,b,xy', 4),
(code2, 'f(a,b,abc=1,abd=4,', 5), (code2, 'g(a,b,xyz=', 4),
(code2, 'f(a,b,abc=1,abd=4,lala', 5), (code2, 'g(a,b,xy=', 5),
(code2, 'f(a,b,abc=1,abd=4,lala=', 5), (code2, 'g(a,b,abc=1,abd=4,', 4),
(code2, 'f(a,b,kw', 5), (code2, 'g(a,b,abc=1,xyz=3,abd=4,', 5),
(code2, 'f(a,b,kwargs=', 5), (code2, 'g(a,b,abc=1,abd=4,lala', 5),
(code2, 'f(u=', 5), (code2, 'g(a,b,abc=1,abd=4,lala=', 5),
(code2, 'f(v=', 1), (code2, 'g(a,b,abc=1,abd=4,abd=', 5),
(code2, 'g(a,b,kw', 5),
(code2, 'g(a,b,kwargs=', 5),
(code2, 'g(u=', 5),
(code2, 'g(v=', 1),
# *args # *args
(code3, 'f(a,b,c', 2), (code3, 'h(a,b,c', 2),
(code3, 'f(a,b,c,', 2), (code3, 'h(a,b,c,', 2),
(code3, 'f(a,b,c,d', 2), (code3, 'h(a,b,c,d', 2),
(code3, 'f(a,b,c,d[', 2), (code3, 'h(a,b,c,d[', 2),
(code3, 'f(a,b,c,d(3,', 2), (code3, 'h(a,b,c,(3,', 2),
(code3, 'f(a,b,c,(3,)', 2), (code3, 'h(a,b,c,(3,)', 2),
(code3, 'f(a,b,args=', None), (code3, 'h(a,b,args=', None),
(code3, 'f(a,b=', 1), (code3, 'h(u,v=', 1),
(code3, 'f(a=', None), (code3, 'h(u=', None),
# *args, **kwargs # *args, **kwargs
(code4, 'f(a,b,c,d', 2), (code4, 'i(a,b,c,d', 2),
(code4, 'f(a,b,c,d,e', 2), (code4, 'i(a,b,c,d,e', 2),
(code4, 'f(a,b,c,d,e=', 5), (code4, 'i(a,b,c,d,e=', 5),
(code4, 'f(a,b,c,d,e=3', 5), (code4, 'i(a,b,c,d,e=3', 5),
(code4, 'f(a,b,c,d=,', 3), #(code4, 'i(a,b,c,d=,x=', 3),
(code4, 'f(a,b,c,d=,x=', 3), (code4, 'i(a,b,c,d=5,x=4', 3),
(code4, 'f(a,b,c,d=5,x=4', 3), (code4, 'i(a,b,c,d=5,x=4,y', 4),
(code4, 'f(a,b,c,d=5,x=4,y', 4), (code4, 'i(a,b,c,d=5,x=4,y=3,', 5),
(code4, 'f(a,b,c,d=5,x=4,y=3,', 5), (code4, 'i(a,b,c,d=5,y=4,x=3,', 5),
(code4, 'f(a,b,c,d=5,y=4,x=3', 4), (code4, 'i(a,b,c,d=4,', 3),
(code4, 'f(a,b,c,d=5,y=4,x=3,', 5), (code4, 'i(a,b,c,x=1,d=2,', 4),
(code4, 'f(a,b,c,d=4,', 5),
(code4, 'f(a,b,c,d=,', 5),
] ]
) )
def test_signature_index(skip_pre_python38, Script, code, call, index): def test_signature_index(skip_pre_python38, Script, code, call, expected_index):
sig, = Script(code + '\n' + call).call_signatures() sig, = Script(code + '\n' + call).call_signatures()
print(call) index = sig.index
print('index', index) assert expected_index == index
assert index == sig.index
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't support __signature__") @pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't support __signature__")