mirror of
https://github.com/davidhalter/jedi.git
synced 2026-11-06 07:02:53 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b91f57bec2 | ||
|
|
c7ceec72f1 | ||
|
|
11e1a965be | ||
|
|
65112c72c7 | ||
|
|
897cbff748 | ||
|
|
2af1a0f394 | ||
|
|
3fd1c87716 | ||
|
|
e7843ba82c | ||
|
|
5d3295e0f9 | ||
|
|
91206f4c65 | ||
|
|
8a56168d9a | ||
|
|
a94b899c13 | ||
|
|
dd7d4a92f4 | ||
|
|
ea1b96ff10 |
No files matched your search
@@ -71,6 +71,8 @@ Code Contributors
|
||||
- Dhanraj E (@Dhanu-dynamic) <dhanraj@labs.digiscrypt.com>
|
||||
- Charan Rathore (@charan-rathore)
|
||||
- Abdullah Ahmed (@abdull-ah-med)
|
||||
- spicysharkco (@spicysharkco)
|
||||
- Don Yin (@Don-Yin) <Don_Yin@outlook.com>
|
||||
|
||||
And a few more "anonymous" contributors.
|
||||
|
||||
|
||||
@@ -6,3 +6,7 @@ Pull Requests are great.
|
||||
4. Push to your fork and submit a pull request.
|
||||
|
||||
**Try to use the PEP8 style guide** (and it's ok to have a line length of 100 characters).
|
||||
|
||||
# LLM usage
|
||||
|
||||
LLM usage is prohibited unless done by maintainers or people who understand what they do.
|
||||
@@ -8,6 +8,7 @@ debug messages to stdout, simply call :func:`set_debug_function` without
|
||||
arguments.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import parso
|
||||
from parso.python import tree
|
||||
@@ -189,7 +190,7 @@ class Script:
|
||||
|
||||
@recursion_limit
|
||||
@validate_line_column
|
||||
def complete(self, line=None, column=None, *, fuzzy=False):
|
||||
def complete(self, line=None, column=None, *, fuzzy=False) -> list[classes.Completion]:
|
||||
"""
|
||||
Completes objects under the cursor.
|
||||
|
||||
@@ -338,7 +339,7 @@ class Script:
|
||||
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
|
||||
have all possible definitions in a file you can also provide an empty
|
||||
|
||||
+8
-5
@@ -9,6 +9,7 @@ from textwrap import dedent
|
||||
from itertools import chain
|
||||
from functools import wraps
|
||||
from inspect import Parameter
|
||||
from typing import Callable, TypeVar, Any, ParamSpec
|
||||
|
||||
from parso.python.parser import Parser
|
||||
from parso.python import tree
|
||||
@@ -20,6 +21,8 @@ from jedi.inference.compiled import get_string_value_set
|
||||
from jedi.cache import signature_time_cache, memoize_method
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
|
||||
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
||||
|
||||
@@ -42,7 +45,7 @@ def higher_recursion_limit():
|
||||
sys.setrecursionlimit(old_limit)
|
||||
|
||||
|
||||
def recursion_limit(func):
|
||||
def recursion_limit(func: Callable[P, T]) -> Callable[P, T]:
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
with higher_recursion_limit():
|
||||
@@ -509,9 +512,9 @@ def cache_signatures(inference_state, context, bracket_leaf, code_lines, user_po
|
||||
)
|
||||
|
||||
|
||||
def validate_line_column(func):
|
||||
def validate_line_column(func: Callable[P, T]) -> Callable[P, T]:
|
||||
@wraps(func)
|
||||
def wrapper(self, line=None, column=None, *args, **kwargs):
|
||||
def wrapper(self, line=None, column=None, *args, **kwargs) -> Any:
|
||||
line = max(len(self._code_lines), 1) if line is None else line
|
||||
if not (0 < line <= len(self._code_lines)):
|
||||
raise ValueError('`line` parameter is not in a valid range.')
|
||||
@@ -528,8 +531,8 @@ def validate_line_column(func):
|
||||
raise ValueError('`column` parameter (%d) is not in a valid range '
|
||||
'(0-%d) for line %d (%r).' % (
|
||||
column, line_len, line, line_string))
|
||||
return func(self, line, column, *args, **kwargs)
|
||||
return wrapper
|
||||
return func(self, line, column, *args, **kwargs) # type: ignore[arg-type]
|
||||
return wrapper # type: ignore[return-value]
|
||||
|
||||
|
||||
def get_module_names(module, all_scopes, definitions=True, references=False):
|
||||
|
||||
@@ -247,6 +247,7 @@ def extract_function(inference_state, path, module_context, name, pos, until_pos
|
||||
code_block = dedent(code_block)
|
||||
if not has_ending_return_stmt:
|
||||
output_var_str = ', '.join(return_variables)
|
||||
if return_variables:
|
||||
code_block += 'return ' + output_var_str + '\n'
|
||||
|
||||
# Check if we have to raise RefactoringError
|
||||
@@ -282,8 +283,10 @@ def extract_function(inference_state, path, module_context, name, pos, until_pos
|
||||
else:
|
||||
if has_ending_return_stmt:
|
||||
replacement = 'return ' + function_call + '\n'
|
||||
else:
|
||||
elif return_variables:
|
||||
replacement = output_var_str + ' = ' + function_call + '\n'
|
||||
else:
|
||||
replacement = function_call + '\n'
|
||||
|
||||
replacement_dct = _replace(nodes, replacement, function_code, pos,
|
||||
insert_before_leaf, remaining_prefix)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
To use Jedi completion in Python interpreter, add the following in your shell
|
||||
setup (e.g., ``.bashrc``). This works only on Linux/Mac, because readline is
|
||||
not available on Windows. If you still want Jedi autocompletion in your REPL,
|
||||
just use IPython instead::
|
||||
setup (e.g., ``.bashrc``). Jedi configures the classic readline shell and
|
||||
Python 3.13+'s new ``_pyrepl`` shell when those hooks are available.
|
||||
You might also want to try IPython instead, which also uses Jedi::
|
||||
|
||||
export PYTHONSTARTUP="$(python -m jedi repl)"
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ class Value(HelperValueMixin):
|
||||
def is_namespace(self):
|
||||
return False
|
||||
|
||||
def is_compiled(self):
|
||||
def is_compiled(self) -> bool:
|
||||
return False
|
||||
|
||||
def is_bound_method(self):
|
||||
|
||||
@@ -256,12 +256,11 @@ class CompiledValue(Value):
|
||||
arg.execute_annotation(context)
|
||||
for arg in arguments)
|
||||
elif name:
|
||||
# While with_generics only exists on very specific objects, we
|
||||
# should probably be fine, because we control all the typing
|
||||
# objects.
|
||||
# These are typically typing module objects. Try to use that to create generics.
|
||||
return ValueSet([
|
||||
v.with_generics(arguments)
|
||||
for v in self.inference_state.typing_module.py__getattribute__(name)
|
||||
if hasattr(v, "with_generics")
|
||||
]).execute_annotation(context)
|
||||
return super().execute_annotation(context)
|
||||
|
||||
|
||||
@@ -395,6 +395,12 @@ class CompForContext(TreeContextMixin, AbstractContext):
|
||||
def py__name__(self):
|
||||
return '<comprehension context>'
|
||||
|
||||
def get_qualified_names(self) -> Any:
|
||||
names = self.parent_context.get_qualified_names()
|
||||
if names is None:
|
||||
return None
|
||||
return names + ('<comprehension>',)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, self.tree_node)
|
||||
|
||||
|
||||
@@ -254,4 +254,4 @@ def _error_argument_count(funcdef, actual_count):
|
||||
else:
|
||||
before = 'from %s to ' % (len(params) - default_arguments)
|
||||
return ('TypeError: %s() takes %s%s arguments (%s given).'
|
||||
% (funcdef.name, before, len(params), actual_count))
|
||||
% (getattr(funcdef, "name", "<lambda>"), before, len(params), actual_count))
|
||||
+29
-13
@@ -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).
|
||||
"""
|
||||
from inspect import Parameter
|
||||
from typing import Sequence
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -93,8 +94,24 @@ def _remove_given_params(arguments, param_names):
|
||||
yield p
|
||||
|
||||
|
||||
class ParamNameFixedKind(ParamNameWrapper):
|
||||
def __init__(self, param_name, new_kind):
|
||||
super().__init__(param_name)
|
||||
self._new_kind = new_kind
|
||||
|
||||
def get_kind(self):
|
||||
return self._new_kind
|
||||
|
||||
|
||||
@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 is_big_annoying_library(param_names[0].parent_context):
|
||||
# At first this feature can look innocent, but it does a lot of
|
||||
@@ -112,13 +129,21 @@ def process_params(param_names, star_count=3): # default means both * and **
|
||||
original_arg_name = None
|
||||
original_kwarg_name = None
|
||||
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()
|
||||
if kind == Parameter.VAR_POSITIONAL:
|
||||
if star_count & 1:
|
||||
if not is_recursion():
|
||||
arg_callables = _iter_nodes_for_param(p)
|
||||
original_arg_name = p
|
||||
elif p.get_kind() == Parameter.VAR_KEYWORD:
|
||||
if star_count & 2:
|
||||
if not is_recursion():
|
||||
kwarg_callables = list(_iter_nodes_for_param(p))
|
||||
original_kwarg_name = p
|
||||
elif kind == Parameter.KEYWORD_ONLY:
|
||||
@@ -157,7 +182,7 @@ def process_params(param_names, star_count=3): # default means both * and **
|
||||
list(_remove_given_params(
|
||||
arguments,
|
||||
signature.get_param_names(resolve_stars=False)
|
||||
)), new_star_count):
|
||||
)), new_star_count, already_used):
|
||||
if p.get_kind() == Parameter.VAR_KEYWORD:
|
||||
kwarg_names.append(p)
|
||||
elif p.get_kind() == Parameter.VAR_POSITIONAL:
|
||||
@@ -190,7 +215,7 @@ def process_params(param_names, star_count=3): # default means both * and **
|
||||
list(_remove_given_params(
|
||||
arguments,
|
||||
signature.get_param_names(resolve_stars=False)
|
||||
)), star_count=2):
|
||||
)), star_count=2, already_used=already_used):
|
||||
if p.get_kind() == Parameter.VAR_KEYWORD:
|
||||
kwarg_names.append(p)
|
||||
elif p.get_kind() == Parameter.KEYWORD_ONLY:
|
||||
@@ -206,12 +231,3 @@ def process_params(param_names, star_count=3): # default means both * and **
|
||||
yield original_kwarg_name
|
||||
elif kwarg_names:
|
||||
yield kwarg_names[0]
|
||||
|
||||
|
||||
class ParamNameFixedKind(ParamNameWrapper):
|
||||
def __init__(self, param_name, new_kind):
|
||||
super().__init__(param_name)
|
||||
self._new_kind = new_kind
|
||||
|
||||
def get_kind(self):
|
||||
return self._new_kind
|
||||
@@ -2,6 +2,7 @@
|
||||
import functools
|
||||
import re
|
||||
import os
|
||||
from typing import ParamSpec, TypeVar, Callable, Iterable
|
||||
|
||||
|
||||
_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)))
|
||||
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):
|
||||
return list(func(*args, **kwargs))
|
||||
return wrapper
|
||||
|
||||
@@ -435,6 +435,10 @@ def _find_overload_functions(context, tree_node):
|
||||
if dotted_name.type == 'name' and dotted_name.value == 'overload':
|
||||
# TODO check with values if it's the right overload
|
||||
return True
|
||||
if dotted_name.type == 'atom_expr':
|
||||
trailer = dotted_name.children[-1]
|
||||
if trailer.children[0] == "." and trailer.children[1].value == "overload":
|
||||
return True
|
||||
return False
|
||||
|
||||
if tree_node.type == 'lambdef':
|
||||
|
||||
+41
-11
@@ -100,17 +100,10 @@ def setup_readline(namespace_module=__main__, fuzzy=False):
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Need to import this one as well to make sure it's executed before
|
||||
# this code. This didn't use to be an issue until 3.3. Starting with
|
||||
# 3.4 this is different, it always overwrites the completer if it's not
|
||||
# already imported here.
|
||||
import rlcompleter # noqa: F401
|
||||
import readline
|
||||
except ImportError:
|
||||
print("Jedi: Module readline not available.")
|
||||
else:
|
||||
readline.set_completer(JediRL().complete)
|
||||
jedi_rl = JediRL()
|
||||
|
||||
def configure_readline(readline):
|
||||
readline.set_completer(jedi_rl.complete)
|
||||
readline.parse_and_bind("tab: complete")
|
||||
# jedi itself does the case matching
|
||||
readline.parse_and_bind("set completion-ignore-case on")
|
||||
@@ -122,6 +115,43 @@ def setup_readline(namespace_module=__main__, fuzzy=False):
|
||||
# No delimiters, Jedi handles that.
|
||||
readline.set_completer_delims('')
|
||||
|
||||
try:
|
||||
import _pyrepl.readline as pyrepl_readline # type: ignore
|
||||
except ImportError:
|
||||
pyrepl_readline = None
|
||||
else:
|
||||
original_setup = getattr(pyrepl_readline, '_setup', None)
|
||||
if original_setup is not None and not getattr(original_setup, '__jedi_wrapped__', False):
|
||||
def _jedi_pyrepl_setup(namespace):
|
||||
original_setup(namespace)
|
||||
configure_readline(pyrepl_readline)
|
||||
wrapper = getattr(pyrepl_readline, '_wrapper', None)
|
||||
if wrapper is not None:
|
||||
# Reset the original completer to None so Jedi's readline
|
||||
# completer is preferred.
|
||||
wrapper.config.module_completer = _NoPyreplModuleCompleter()
|
||||
|
||||
_jedi_pyrepl_setup.__jedi_wrapped__ = True # type: ignore[attr-defined]
|
||||
pyrepl_readline._setup = _jedi_pyrepl_setup
|
||||
|
||||
try:
|
||||
# Need to import this one as well to make sure it's executed before
|
||||
# this code. This didn't use to be an issue until 3.3. Starting with
|
||||
# 3.4 this is different, it always overwrites the completer if it's not
|
||||
# already imported here.
|
||||
import rlcompleter # noqa: F401
|
||||
import readline
|
||||
except ImportError:
|
||||
if pyrepl_readline is None:
|
||||
print("Jedi: Module readline not available.")
|
||||
else:
|
||||
configure_readline(readline)
|
||||
|
||||
|
||||
class _NoPyreplModuleCompleter:
|
||||
def get_completions(self, line, include_values=False):
|
||||
return None
|
||||
|
||||
|
||||
def version_info():
|
||||
"""
|
||||
|
||||
@@ -50,7 +50,7 @@ setup(name='jedi',
|
||||
'typing_extensions',
|
||||
# latest version on 2025-06-16
|
||||
'flake8==7.1.2',
|
||||
'zuban==0.7.0',
|
||||
'zuban==0.10.1',
|
||||
# Arbitrary pins, latest at the time of pinning
|
||||
'types-setuptools==80.9.0.20250529',
|
||||
],
|
||||
|
||||
@@ -86,3 +86,28 @@ def broken_f1(value: Tuple[T]) -> Tuple[T]: ...
|
||||
tup: Tuple[float]
|
||||
#? float()
|
||||
broken_f1(broken_f1(tup))[0]
|
||||
|
||||
# -------------------------
|
||||
# Typing Extensions
|
||||
# -------------------------
|
||||
|
||||
import typing_extensions
|
||||
import typing
|
||||
|
||||
@typing_extensions.overload
|
||||
def extensions(x: int) -> int: ...
|
||||
@typing_extensions.overload
|
||||
def extensions(x: str) -> str: ...
|
||||
#? int()
|
||||
extensions(1)
|
||||
#? str()
|
||||
extensions("")
|
||||
|
||||
@typing.overload
|
||||
def typing_direct(x: int) -> int: ...
|
||||
@typing.overload
|
||||
def typing_direct(x: str) -> str: ...
|
||||
#? int()
|
||||
typing_direct(1)
|
||||
#? str()
|
||||
typing_direct("")
|
||||
@@ -314,6 +314,22 @@ def x(v1):
|
||||
#bar
|
||||
return x
|
||||
x
|
||||
# -------------------------------------------------- with-range-func-no-output
|
||||
def x(v1):
|
||||
#? 2 text {'new_name': 'func', 'until_line': 4}
|
||||
print(v1)
|
||||
print(v1 + 1)
|
||||
return v1
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
def func(v1):
|
||||
print(v1)
|
||||
print(v1 + 1)
|
||||
|
||||
|
||||
def x(v1):
|
||||
#? 2 text {'new_name': 'func', 'until_line': 4}
|
||||
func(v1)
|
||||
return v1
|
||||
# -------------------------------------------------- in-class-range-1
|
||||
class X1:
|
||||
#? 9 text {'new_name': 'f', 'until_line': 4}
|
||||
|
||||
@@ -128,3 +128,45 @@ def test_variable_in_func(Script):
|
||||
x = names[-1]
|
||||
assert x.name == 'x'
|
||||
assert x.full_name == '__main__.f.x'
|
||||
|
||||
|
||||
def test_comprehension_full_name_issue_1944(Script):
|
||||
source = textwrap.dedent('''\
|
||||
def pig_latin(text):
|
||||
translated_words = [translate(word) for word in words]
|
||||
''')
|
||||
names = Script(source).get_names(all_scopes=True, references=True)
|
||||
word_names = [name for name in names if name.name == 'word']
|
||||
assert len(word_names) == 2
|
||||
assert {name.full_name for name in word_names} == {
|
||||
'__main__.pig_latin.<comprehension>.word'
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('expression', [
|
||||
'[word for word in words]',
|
||||
'{word for word in words}',
|
||||
'{word: word for word in words}',
|
||||
'(word for word in words)',
|
||||
'[word for row in rows for word in row]',
|
||||
'[word for row in rows if row for word in row]',
|
||||
])
|
||||
def test_comprehension_full_name(Script, expression):
|
||||
source = 'def f():\n return ' + expression
|
||||
names = Script(source).get_names(all_scopes=True, references=True)
|
||||
word_names = [name for name in names if name.name == 'word']
|
||||
assert len(word_names) >= 2
|
||||
assert {name.full_name for name in word_names} == {'__main__.f.<comprehension>.word'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('source, expected', [
|
||||
('[word for word in words]', '__main__.<comprehension>.word'),
|
||||
('def f():\n return [[word for word in words] for words in rows]',
|
||||
'__main__.f.<comprehension>.<comprehension>.word'),
|
||||
('def outer():\n def f():\n return [word for word in words]', None),
|
||||
])
|
||||
def test_comprehension_parent_full_name(Script, source, expected):
|
||||
names = Script(source).get_names(all_scopes=True, references=True)
|
||||
word_names = [name for name in names if name.name == 'word']
|
||||
assert len(word_names) == 2
|
||||
assert {name.full_name for name in word_names} == {expected}
|
||||
@@ -873,3 +873,20 @@ def test_whitespace_after_dot_completion(Script):
|
||||
# From #1954
|
||||
completions = jedi.Interpreter("object. \n", []).complete(1, 8)
|
||||
assert "mro" in [c.name for c in completions]
|
||||
|
||||
|
||||
def test_interpreter_with_literals_no_crash():
|
||||
# From #1990
|
||||
class A:
|
||||
@property
|
||||
def value(self) -> typing.Literal["a"]:
|
||||
return "a"
|
||||
|
||||
a = A()
|
||||
|
||||
interpreter = jedi.Interpreter("a.", [{"a": a}])
|
||||
value = next(filter(lambda c: c.name == "value", interpreter.complete()))
|
||||
assert value.name == "value"
|
||||
|
||||
interpreter = jedi.Interpreter("a.value.", [{"a": a}])
|
||||
assert "upper" in [c.name for c in interpreter.complete()]
|
||||
@@ -1,3 +1,5 @@
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
_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):
|
||||
s, = Script('staticmethod(').get_signatures()
|
||||
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)'
|
||||
@@ -165,7 +165,7 @@ def test_tree_signature(Script, environment, code, expected):
|
||||
('two_kwargs_redirects(kw, kw)', '*args, a, b, c, **kwargs'),
|
||||
('two_kwargs_redirects(simple, kw)', '*args, a, b, c, **kwargs'),
|
||||
('two_kwargs_redirects(simple2, two_kwargs_redirects(simple, simple))',
|
||||
'*args, x, a, b, c'),
|
||||
'*args, x, **kwargs'),
|
||||
|
||||
('combined_redirect(simple, simple2)', 'a, b, /, *, x'),
|
||||
('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'),
|
||||
@@ -180,11 +180,11 @@ def test_tree_signature(Script, environment, code, expected):
|
||||
('combined_lot_of_args(simple4, kw)', '*, b, c, **kwargs'),
|
||||
|
||||
('combined_redirect(combined_redirect(simple2, simple4), combined_redirect(kw, simple5))',
|
||||
'x, /, *, y'),
|
||||
'*args, **kwargs'),
|
||||
('combined_redirect(combined_redirect(simple4, simple2), combined_redirect(simple5, kw))',
|
||||
'a, b, x: int, /, *, a, b, c, **kwargs'),
|
||||
'*args, **kwargs'),
|
||||
('combined_redirect(combined_redirect(a, kw), combined_redirect(kw, simple5))',
|
||||
'a, b, /, *args, y'),
|
||||
'*args, **kwargs'),
|
||||
|
||||
('no_redirect(kw)', '*args, **kwargs'),
|
||||
('no_redirect(akw)', '*args, **kwargs'),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import types
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
@@ -136,3 +137,29 @@ class TestSetupReadline(unittest.TestCase):
|
||||
|
||||
def test_version_info():
|
||||
assert utils.version_info()[:2] > (0, 7)
|
||||
|
||||
|
||||
def test_pyrepl():
|
||||
try:
|
||||
from _pyrepl import readline # zuban: ignore[import-not-found]
|
||||
except ImportError:
|
||||
return # This is typical before Python 3.13
|
||||
|
||||
utils.setup_readline(types.SimpleNamespace())
|
||||
readline._setup({})
|
||||
|
||||
def complete(text):
|
||||
completer = readline.get_completer()
|
||||
i = 0
|
||||
completions = []
|
||||
while True:
|
||||
completion = completer(text, i)
|
||||
if completion is None:
|
||||
break
|
||||
completions.append(completion)
|
||||
i += 1
|
||||
return completions
|
||||
|
||||
assert complete("str") == ["str"]
|
||||
assert complete("str().upp") == ["str().upper"]
|
||||
assert complete("import multiproc") == ["import multiprocessing"]
|
||||
Reference in new issue
Block a user