mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14:27 +08:00
Completion: Don't suggest variables when only kwargs are legal, fixes #1541
This commit is contained in:
@@ -30,16 +30,43 @@ class ParamNameWithEquals(ParamNameWrapper):
|
||||
return self.string_name + '='
|
||||
|
||||
|
||||
def get_signature_param_names(signatures):
|
||||
# add named params
|
||||
def _get_signature_param_names(signatures, positional_count, used_kwargs):
|
||||
# Add named params
|
||||
for call_sig in signatures:
|
||||
for p in call_sig.params:
|
||||
for i, p in enumerate(call_sig.params):
|
||||
# Allow protected access, because it's a public API.
|
||||
if p._name.get_kind() in (Parameter.POSITIONAL_OR_KEYWORD,
|
||||
Parameter.KEYWORD_ONLY):
|
||||
# TODO reconsider with Python 2 drop
|
||||
kind = p._name.get_kind()
|
||||
if i < positional_count and kind == Parameter.POSITIONAL_OR_KEYWORD:
|
||||
continue
|
||||
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) \
|
||||
and p.name not in used_kwargs:
|
||||
yield ParamNameWithEquals(p._name)
|
||||
|
||||
|
||||
def _must_be_kwarg(signatures, positional_count, used_kwargs):
|
||||
if used_kwargs:
|
||||
return True
|
||||
|
||||
must_be_kwarg = True
|
||||
for signature in signatures:
|
||||
for i, p in enumerate(signature.params):
|
||||
# TODO reconsider with Python 2 drop
|
||||
kind = p._name.get_kind()
|
||||
if kind is Parameter.VAR_POSITIONAL:
|
||||
# In case there were not already kwargs, the next param can
|
||||
# always be a normal argument.
|
||||
return False
|
||||
|
||||
if i >= positional_count and kind in (Parameter.POSITIONAL_OR_KEYWORD,
|
||||
Parameter.POSITIONAL_ONLY):
|
||||
must_be_kwarg = False
|
||||
break
|
||||
if not must_be_kwarg:
|
||||
break
|
||||
return must_be_kwarg
|
||||
|
||||
|
||||
def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name):
|
||||
comp_dct = set()
|
||||
if settings.case_insensitive_completion:
|
||||
@@ -264,20 +291,34 @@ class Completion:
|
||||
elif self._is_parameter_completion():
|
||||
completion_names += self._complete_params(leaf)
|
||||
else:
|
||||
completion_names += self._complete_global_scope()
|
||||
completion_names += self._complete_inherited(is_function=False)
|
||||
# Apparently this looks like it's good enough to filter most cases
|
||||
# so that signature completions don't randomly appear.
|
||||
# To understand why this works, three things are important:
|
||||
# 1. trailer with a `,` in it is either a subscript or an arglist.
|
||||
# 2. If there's no `,`, it's at the start and only signatures start
|
||||
# with `(`. Other trailers could start with `.` or `[`.
|
||||
# 3. Decorators are very primitive and have an optional `(` with
|
||||
# optional arglist in them.
|
||||
kwargs_only = False
|
||||
if nodes[-1] in ['(', ','] \
|
||||
and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
|
||||
signatures = self._signatures_callback(*self._position)
|
||||
if signatures:
|
||||
call_details = signatures[0]._call_details
|
||||
used_kwargs = list(call_details.iter_used_keyword_arguments())
|
||||
positional_count = call_details.count_positional_arguments()
|
||||
|
||||
# Apparently this looks like it's good enough to filter most cases
|
||||
# so that signature completions don't randomly appear.
|
||||
# To understand why this works, three things are important:
|
||||
# 1. trailer with a `,` in it is either a subscript or an arglist.
|
||||
# 2. If there's no `,`, it's at the start and only signatures start
|
||||
# with `(`. Other trailers could start with `.` or `[`.
|
||||
# 3. Decorators are very primitive and have an optional `(` with
|
||||
# optional arglist in them.
|
||||
if nodes[-1] in ['(', ','] and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
|
||||
signatures = self._signatures_callback(*self._position)
|
||||
completion_names += get_signature_param_names(signatures)
|
||||
completion_names += _get_signature_param_names(
|
||||
signatures,
|
||||
positional_count,
|
||||
used_kwargs,
|
||||
)
|
||||
|
||||
kwargs_only = _must_be_kwarg(signatures, positional_count, used_kwargs)
|
||||
|
||||
if not kwargs_only:
|
||||
completion_names += self._complete_global_scope()
|
||||
completion_names += self._complete_inherited(is_function=False)
|
||||
|
||||
return cached_name, completion_names
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from jedi.inference.base_value import NO_VALUES
|
||||
from jedi.inference.syntax_tree import infer_atom
|
||||
from jedi.inference.helpers import infer_call_of_leaf
|
||||
from jedi.inference.compiled import get_string_value_set
|
||||
from jedi.cache import signature_time_cache
|
||||
from jedi.cache import signature_time_cache, memoize_method
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
@@ -216,11 +216,15 @@ class CallDetails(object):
|
||||
def keyword_name_str(self):
|
||||
return _get_index_and_key(self._children, self._position)[1]
|
||||
|
||||
@memoize_method
|
||||
def _list_arguments(self):
|
||||
return list(_iter_arguments(self._children, self._position))
|
||||
|
||||
def calculate_index(self, param_names):
|
||||
positional_count = 0
|
||||
used_names = set()
|
||||
star_count = -1
|
||||
args = list(_iter_arguments(self._children, self._position))
|
||||
args = self._list_arguments()
|
||||
if not args:
|
||||
if param_names:
|
||||
return 0
|
||||
@@ -267,6 +271,19 @@ class CallDetails(object):
|
||||
return i
|
||||
return None
|
||||
|
||||
def iter_used_keyword_arguments(self):
|
||||
for star_count, key_start, had_equal in list(self._list_arguments()):
|
||||
if had_equal and key_start:
|
||||
yield key_start
|
||||
|
||||
def count_positional_arguments(self):
|
||||
count = 0
|
||||
for star_count, key_start, had_equal in self._list_arguments()[:-1]:
|
||||
if star_count:
|
||||
break
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def _iter_arguments(nodes, position):
|
||||
def remove_after_pos(name):
|
||||
|
||||
@@ -89,3 +89,52 @@ def x(): pass
|
||||
#? 8 ['xyz=']
|
||||
@foo(xyz)
|
||||
def x(): pass
|
||||
|
||||
# -----------------
|
||||
# Only keyword arguments are valid
|
||||
# -----------------
|
||||
# python >= 3.5
|
||||
|
||||
def x(bam, *, bar, baz):
|
||||
pass
|
||||
def y(bam, *bal, bar, baz, **bag):
|
||||
pass
|
||||
def z(bam, bar=2, *, bas=1):
|
||||
pass
|
||||
|
||||
#? 7 ['bar=', 'baz=']
|
||||
x(1, ba)
|
||||
|
||||
#? 14 ['baz=']
|
||||
x(1, bar=2, ba)
|
||||
#? 7 ['bar=', 'baz=']
|
||||
x(1, ba, baz=3)
|
||||
#? 14 ['baz=']
|
||||
x(1, bar=2, baz=3)
|
||||
#? 7 ['BaseException']
|
||||
x(basee)
|
||||
#? 22 ['bar=', 'baz=']
|
||||
x(1, 2, 3, 4, 5, 6, bar=2)
|
||||
|
||||
#? 14 ['baz=']
|
||||
y(1, bar=2, ba)
|
||||
#? 7 ['bar=', 'BaseException', 'baz=']
|
||||
y(1, ba, baz=3)
|
||||
#? 14 ['baz=']
|
||||
y(1, bar=2, baz=3)
|
||||
#? 7 ['BaseException']
|
||||
y(basee)
|
||||
#? 22 ['bar=', 'BaseException', 'baz=']
|
||||
y(1, 2, 3, 4, 5, 6, bar=2)
|
||||
|
||||
#? 11 ['bar=', 'bas=']
|
||||
z(bam=1, bar=2, bas=3)
|
||||
#? 8 ['BaseException', 'bas=']
|
||||
z(1, bas=2)
|
||||
#? 12 ['BaseException']
|
||||
z(1, bas=bas)
|
||||
|
||||
#? 19 ['dict']
|
||||
z(1, bas=bas, **dic)
|
||||
#? 18 ['dict']
|
||||
z(1, bas=bas, *dic)
|
||||
|
||||
Reference in New Issue
Block a user