mirror of
https://github.com/davidhalter/jedi.git
synced 2026-10-18 18:51:26 +08:00
Avoid recursions in signatures, fixes #2085
This commit is contained in:
1 parent
e7843ba82c
commit
3fd1c87716
4 files changed
+53
-8
No files matched your search
@@ -8,6 +8,7 @@ debug messages to stdout, simply call :func:`set_debug_function` without
|
|||||||
arguments.
|
arguments.
|
||||||
"""
|
"""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import parso
|
import parso
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
@@ -338,7 +339,7 @@ class Script:
|
|||||||
fuzzy=fuzzy,
|
fuzzy=fuzzy,
|
||||||
)
|
)
|
||||||
|
|
||||||
def complete_search(self, string, **kwargs):
|
def complete_search(self, string, **kwargs) -> list[Any]:
|
||||||
"""
|
"""
|
||||||
Like :meth:`.Script.search`, but completes that string. If you want to
|
Like :meth:`.Script.search`, but completes that string. If you want to
|
||||||
have all possible definitions in a file you can also provide an empty
|
have all possible definitions in a file you can also provide an empty
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ This means for example in this case::
|
|||||||
The signature here for bar should be `bar(b, c)` instead of bar(*args).
|
The signature here for bar should be `bar(b, c)` instead of bar(*args).
|
||||||
"""
|
"""
|
||||||
from inspect import Parameter
|
from inspect import Parameter
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
from jedi.inference.utils import to_list
|
from jedi.inference.utils import to_list
|
||||||
from jedi.inference.names import ParamNameWrapper
|
from jedi.inference.names import ParamNameWrapper, AnonymousParamName
|
||||||
from jedi.inference.helpers import is_big_annoying_library
|
from jedi.inference.helpers import is_big_annoying_library
|
||||||
|
|
||||||
|
|
||||||
@@ -94,7 +95,11 @@ def _remove_given_params(arguments, param_names):
|
|||||||
|
|
||||||
|
|
||||||
@to_list
|
@to_list
|
||||||
def process_params(param_names, star_count=3): # default means both * and **
|
def process_params(param_names: Sequence[AnonymousParamName | ParamNameFixedKind], star_count=3, already_used=None):
|
||||||
|
# start_count=3 means both * and **
|
||||||
|
if already_used is None:
|
||||||
|
already_used = set()
|
||||||
|
|
||||||
if param_names:
|
if param_names:
|
||||||
if is_big_annoying_library(param_names[0].parent_context):
|
if is_big_annoying_library(param_names[0].parent_context):
|
||||||
# At first this feature can look innocent, but it does a lot of
|
# At first this feature can look innocent, but it does a lot of
|
||||||
@@ -112,14 +117,22 @@ def process_params(param_names, star_count=3): # default means both * and **
|
|||||||
original_arg_name = None
|
original_arg_name = None
|
||||||
original_kwarg_name = None
|
original_kwarg_name = None
|
||||||
for p in param_names:
|
for p in param_names:
|
||||||
|
def is_recursion():
|
||||||
|
is_recursion = p.tree_name in already_used
|
||||||
|
if not is_recursion:
|
||||||
|
already_used.add(p.tree_name)
|
||||||
|
return is_recursion
|
||||||
|
|
||||||
kind = p.get_kind()
|
kind = p.get_kind()
|
||||||
if kind == Parameter.VAR_POSITIONAL:
|
if kind == Parameter.VAR_POSITIONAL:
|
||||||
if star_count & 1:
|
if star_count & 1:
|
||||||
arg_callables = _iter_nodes_for_param(p)
|
if not is_recursion():
|
||||||
|
arg_callables = _iter_nodes_for_param(p)
|
||||||
original_arg_name = p
|
original_arg_name = p
|
||||||
elif p.get_kind() == Parameter.VAR_KEYWORD:
|
elif p.get_kind() == Parameter.VAR_KEYWORD:
|
||||||
if star_count & 2:
|
if star_count & 2:
|
||||||
kwarg_callables = list(_iter_nodes_for_param(p))
|
if not is_recursion():
|
||||||
|
kwarg_callables = list(_iter_nodes_for_param(p))
|
||||||
original_kwarg_name = p
|
original_kwarg_name = p
|
||||||
elif kind == Parameter.KEYWORD_ONLY:
|
elif kind == Parameter.KEYWORD_ONLY:
|
||||||
if star_count & 2:
|
if star_count & 2:
|
||||||
@@ -157,7 +170,7 @@ def process_params(param_names, star_count=3): # default means both * and **
|
|||||||
list(_remove_given_params(
|
list(_remove_given_params(
|
||||||
arguments,
|
arguments,
|
||||||
signature.get_param_names(resolve_stars=False)
|
signature.get_param_names(resolve_stars=False)
|
||||||
)), new_star_count):
|
)), new_star_count, already_used):
|
||||||
if p.get_kind() == Parameter.VAR_KEYWORD:
|
if p.get_kind() == Parameter.VAR_KEYWORD:
|
||||||
kwarg_names.append(p)
|
kwarg_names.append(p)
|
||||||
elif p.get_kind() == Parameter.VAR_POSITIONAL:
|
elif p.get_kind() == Parameter.VAR_POSITIONAL:
|
||||||
@@ -190,7 +203,7 @@ def process_params(param_names, star_count=3): # default means both * and **
|
|||||||
list(_remove_given_params(
|
list(_remove_given_params(
|
||||||
arguments,
|
arguments,
|
||||||
signature.get_param_names(resolve_stars=False)
|
signature.get_param_names(resolve_stars=False)
|
||||||
)), star_count=2):
|
)), star_count=2, already_used=already_used):
|
||||||
if p.get_kind() == Parameter.VAR_KEYWORD:
|
if p.get_kind() == Parameter.VAR_KEYWORD:
|
||||||
kwarg_names.append(p)
|
kwarg_names.append(p)
|
||||||
elif p.get_kind() == Parameter.KEYWORD_ONLY:
|
elif p.get_kind() == Parameter.KEYWORD_ONLY:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import functools
|
import functools
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
from typing import ParamSpec, TypeVar, Callable, Iterable
|
||||||
|
|
||||||
|
|
||||||
_sep = os.path.sep
|
_sep = os.path.sep
|
||||||
@@ -10,8 +11,11 @@ if os.path.altsep is not None:
|
|||||||
_path_re = re.compile(r'(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
|
_path_re = re.compile(r'(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
|
||||||
del _sep
|
del _sep
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
def to_list(func):
|
|
||||||
|
def to_list(func: Callable[P, Iterable[T]]) -> Callable[P, list[T]]:
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
return list(func(*args, **kwargs))
|
return list(func(*args, **kwargs))
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
_tuple_code = 'from typing import Tuple\ndef f(x: Tuple[int]): ...\nf'
|
_tuple_code = 'from typing import Tuple\ndef f(x: Tuple[int]): ...\nf'
|
||||||
@@ -69,3 +71,28 @@ def test_param_kind_and_name(code, index, param_code, kind, Script):
|
|||||||
def test_staticmethod(Script):
|
def test_staticmethod(Script):
|
||||||
s, = Script('staticmethod(').get_signatures()
|
s, = Script('staticmethod(').get_signatures()
|
||||||
assert s.to_string() == 'staticmethod(f: Callable[_P, _R_co], /)'
|
assert s.to_string() == 'staticmethod(f: Callable[_P, _R_co], /)'
|
||||||
|
|
||||||
|
|
||||||
|
def test_recursive_star_kwargs_do_not_recurse(Script):
|
||||||
|
# From #2085
|
||||||
|
code = dedent('''
|
||||||
|
def test(x, **kwargs):
|
||||||
|
if isinstance(x, int):
|
||||||
|
return test((x,), **kwargs)
|
||||||
|
test(
|
||||||
|
''')
|
||||||
|
script = Script(code)
|
||||||
|
script.complete()
|
||||||
|
signature, = script.get_signatures()
|
||||||
|
assert signature.to_string() == 'test(x, **kwargs)'
|
||||||
|
|
||||||
|
code = dedent('''
|
||||||
|
def test(x, *args, **kwargs):
|
||||||
|
if isinstance(x, int):
|
||||||
|
return test((x,), *args, **kwargs)
|
||||||
|
test(
|
||||||
|
''')
|
||||||
|
script = Script(code)
|
||||||
|
script.complete()
|
||||||
|
signature, = script.get_signatures()
|
||||||
|
assert signature.to_string() == 'test(x, *args, **kwargs)'
|
||||||
Reference in new issue
Block a user