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 + '='
|
return self.string_name + '='
|
||||||
|
|
||||||
|
|
||||||
def get_signature_param_names(signatures):
|
def _get_signature_param_names(signatures, positional_count, used_kwargs):
|
||||||
# add named params
|
# Add named params
|
||||||
for call_sig in signatures:
|
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.
|
# Allow protected access, because it's a public API.
|
||||||
if p._name.get_kind() in (Parameter.POSITIONAL_OR_KEYWORD,
|
# TODO reconsider with Python 2 drop
|
||||||
Parameter.KEYWORD_ONLY):
|
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)
|
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):
|
def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name):
|
||||||
comp_dct = set()
|
comp_dct = set()
|
||||||
if settings.case_insensitive_completion:
|
if settings.case_insensitive_completion:
|
||||||
@@ -264,20 +291,34 @@ class Completion:
|
|||||||
elif self._is_parameter_completion():
|
elif self._is_parameter_completion():
|
||||||
completion_names += self._complete_params(leaf)
|
completion_names += self._complete_params(leaf)
|
||||||
else:
|
else:
|
||||||
completion_names += self._complete_global_scope()
|
# Apparently this looks like it's good enough to filter most cases
|
||||||
completion_names += self._complete_inherited(is_function=False)
|
# 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
|
completion_names += _get_signature_param_names(
|
||||||
# so that signature completions don't randomly appear.
|
signatures,
|
||||||
# To understand why this works, three things are important:
|
positional_count,
|
||||||
# 1. trailer with a `,` in it is either a subscript or an arglist.
|
used_kwargs,
|
||||||
# 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
|
kwargs_only = _must_be_kwarg(signatures, positional_count, used_kwargs)
|
||||||
# optional arglist in them.
|
|
||||||
if nodes[-1] in ['(', ','] and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
|
if not kwargs_only:
|
||||||
signatures = self._signatures_callback(*self._position)
|
completion_names += self._complete_global_scope()
|
||||||
completion_names += get_signature_param_names(signatures)
|
completion_names += self._complete_inherited(is_function=False)
|
||||||
|
|
||||||
return cached_name, completion_names
|
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.syntax_tree import infer_atom
|
||||||
from jedi.inference.helpers import infer_call_of_leaf
|
from jedi.inference.helpers import infer_call_of_leaf
|
||||||
from jedi.inference.compiled import get_string_value_set
|
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
|
from jedi.parser_utils import get_parent_scope
|
||||||
|
|
||||||
|
|
||||||
@@ -216,11 +216,15 @@ 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]
|
||||||
|
|
||||||
|
@memoize_method
|
||||||
|
def _list_arguments(self):
|
||||||
|
return list(_iter_arguments(self._children, self._position))
|
||||||
|
|
||||||
def calculate_index(self, param_names):
|
def calculate_index(self, param_names):
|
||||||
positional_count = 0
|
positional_count = 0
|
||||||
used_names = set()
|
used_names = set()
|
||||||
star_count = -1
|
star_count = -1
|
||||||
args = list(_iter_arguments(self._children, self._position))
|
args = self._list_arguments()
|
||||||
if not args:
|
if not args:
|
||||||
if param_names:
|
if param_names:
|
||||||
return 0
|
return 0
|
||||||
@@ -267,6 +271,19 @@ class CallDetails(object):
|
|||||||
return i
|
return i
|
||||||
return None
|
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 _iter_arguments(nodes, position):
|
||||||
def remove_after_pos(name):
|
def remove_after_pos(name):
|
||||||
|
|||||||
@@ -89,3 +89,52 @@ def x(): pass
|
|||||||
#? 8 ['xyz=']
|
#? 8 ['xyz=']
|
||||||
@foo(xyz)
|
@foo(xyz)
|
||||||
def x(): pass
|
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