mirror of
https://github.com/davidhalter/jedi.git
synced 2026-10-17 23:51:27 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2af1a0f394 | ||
|
|
3fd1c87716 | ||
|
|
e7843ba82c | ||
|
|
5d3295e0f9 | ||
|
|
91206f4c65 | ||
|
|
8a56168d9a | ||
|
|
a94b899c13 | ||
|
|
dd7d4a92f4 | ||
|
|
ea1b96ff10 | ||
|
|
ab89beb06c | ||
|
|
b7009b938a | ||
|
|
142bd1d0be | ||
|
|
575c6ab378 | ||
|
|
c71dead915 | ||
|
|
091735802d | ||
|
|
102700116c | ||
|
|
79d9764bce | ||
|
|
3f7e8adddc | ||
|
|
d8efff8e30 | ||
|
|
2e3e905707 | ||
|
|
558135e8f3 | ||
|
|
f37a9f9feb | ||
|
|
ed47f89162 | ||
|
|
112d255414 | ||
|
|
5c37b736a3 | ||
|
|
19236b472b | ||
|
|
ef5d361121 |
No files matched your search
@@ -66,6 +66,11 @@ Code Contributors
|
|||||||
- Martin Vielsmaier (@moser) <martin@vielsmaier.net>
|
- Martin Vielsmaier (@moser) <martin@vielsmaier.net>
|
||||||
- TingJia Wu (@WutingjiaX) <wutingjia@bytedance.com>
|
- TingJia Wu (@WutingjiaX) <wutingjia@bytedance.com>
|
||||||
- Nguyễn Hồng Quân <ng.hong.quan@gmail.com>
|
- Nguyễn Hồng Quân <ng.hong.quan@gmail.com>
|
||||||
|
- haoran3160-afk (@haoran3160-afk)
|
||||||
|
- Eric3-jp (@Eric3-jp) (with OpenAI Codex assistance)
|
||||||
|
- Dhanraj E (@Dhanu-dynamic) <dhanraj@labs.digiscrypt.com>
|
||||||
|
- Charan Rathore (@charan-rathore)
|
||||||
|
- Abdullah Ahmed (@abdull-ah-med)
|
||||||
|
|
||||||
And a few more "anonymous" contributors.
|
And a few more "anonymous" contributors.
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ Changelog
|
|||||||
Unreleased
|
Unreleased
|
||||||
++++++++++
|
++++++++++
|
||||||
|
|
||||||
|
0.20.1 (2026-10-12)
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
- Avoid loading an executable path from `project.json`
|
||||||
|
- Avoid changing the global recursion limit
|
||||||
|
- Support for async pytest fixtures
|
||||||
|
- Work better with PEP 695 type parameter syntax
|
||||||
|
|
||||||
0.20.0 (2026-05-02)
|
0.20.0 (2026-05-02)
|
||||||
+++++++++++++++++++
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ ad
|
|||||||
load
|
load
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '0.20.0'
|
__version__ = '0.20.1'
|
||||||
|
|
||||||
from jedi.api import Script, Interpreter, set_debug_function, preload_module
|
from jedi.api import Script, Interpreter, set_debug_function, preload_module
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
|
|||||||
+20
-8
@@ -7,8 +7,8 @@ Alternatively, if you don't need a custom function and are happy with printing
|
|||||||
debug messages to stdout, simply call :func:`set_debug_function` without
|
debug messages to stdout, simply call :func:`set_debug_function` without
|
||||||
arguments.
|
arguments.
|
||||||
"""
|
"""
|
||||||
import sys
|
|
||||||
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
|
||||||
@@ -21,7 +21,7 @@ from jedi.file_io import KnownContentFileIO
|
|||||||
from jedi.api import classes
|
from jedi.api import classes
|
||||||
from jedi.api import interpreter
|
from jedi.api import interpreter
|
||||||
from jedi.api import helpers
|
from jedi.api import helpers
|
||||||
from jedi.api.helpers import validate_line_column
|
from jedi.api.helpers import validate_line_column, recursion_limit
|
||||||
from jedi.api.completion import Completion, search_in_module
|
from jedi.api.completion import Completion, search_in_module
|
||||||
from jedi.api.keywords import KeywordName
|
from jedi.api.keywords import KeywordName
|
||||||
from jedi.api.environment import InterpreterEnvironment
|
from jedi.api.environment import InterpreterEnvironment
|
||||||
@@ -43,10 +43,6 @@ from jedi.inference.gradual.conversion import convert_names, convert_values
|
|||||||
from jedi.inference.gradual.utils import load_proper_stub_module
|
from jedi.inference.gradual.utils import load_proper_stub_module
|
||||||
from jedi.inference.utils import to_list
|
from jedi.inference.utils import to_list
|
||||||
|
|
||||||
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
|
|
||||||
# can remove some "maximum recursion depth" errors.
|
|
||||||
sys.setrecursionlimit(3000)
|
|
||||||
|
|
||||||
|
|
||||||
class Script:
|
class Script:
|
||||||
"""
|
"""
|
||||||
@@ -97,6 +93,7 @@ class Script:
|
|||||||
references works well, because the right folder is searched. There are
|
references works well, because the right folder is searched. There are
|
||||||
also ways to modify the sys path and other things.
|
also ways to modify the sys path and other things.
|
||||||
"""
|
"""
|
||||||
|
@recursion_limit
|
||||||
def __init__(self, code=None, *, path=None, environment=None, project=None):
|
def __init__(self, code=None, *, path=None, environment=None, project=None):
|
||||||
self._orig_path = path
|
self._orig_path = path
|
||||||
if isinstance(path, str):
|
if isinstance(path, str):
|
||||||
@@ -191,8 +188,9 @@ class Script:
|
|||||||
self._inference_state.environment,
|
self._inference_state.environment,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@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.
|
Completes objects under the cursor.
|
||||||
|
|
||||||
@@ -214,6 +212,7 @@ class Script:
|
|||||||
)
|
)
|
||||||
return completion.complete()
|
return completion.complete()
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False):
|
def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False):
|
||||||
"""
|
"""
|
||||||
@@ -259,6 +258,7 @@ class Script:
|
|||||||
# the API.
|
# the API.
|
||||||
return helpers.sorted_definitions(set(defs))
|
return helpers.sorted_definitions(set(defs))
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def goto(self, line=None, column=None, *, follow_imports=False, follow_builtin_imports=False,
|
def goto(self, line=None, column=None, *, follow_imports=False, follow_builtin_imports=False,
|
||||||
only_stubs=False, prefer_stubs=False):
|
only_stubs=False, prefer_stubs=False):
|
||||||
@@ -324,6 +324,7 @@ class Script:
|
|||||||
"""
|
"""
|
||||||
return self._search_func(string, all_scopes=all_scopes)
|
return self._search_func(string, all_scopes=all_scopes)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@to_list
|
@to_list
|
||||||
def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
|
def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
|
||||||
names = self._names(all_scopes=all_scopes)
|
names = self._names(all_scopes=all_scopes)
|
||||||
@@ -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
|
||||||
@@ -353,6 +354,7 @@ class Script:
|
|||||||
"""
|
"""
|
||||||
return self._search_func(string, complete=True, **kwargs)
|
return self._search_func(string, complete=True, **kwargs)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def help(self, line=None, column=None):
|
def help(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
@@ -397,6 +399,7 @@ class Script:
|
|||||||
return [classes.Name(self._inference_state, name)]
|
return [classes.Name(self._inference_state, name)]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def get_references(self, line=None, column=None, **kwargs):
|
def get_references(self, line=None, column=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -428,6 +431,7 @@ class Script:
|
|||||||
return helpers.sorted_definitions(definitions)
|
return helpers.sorted_definitions(definitions)
|
||||||
return _references(**kwargs)
|
return _references(**kwargs)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def get_signatures(self, line=None, column=None):
|
def get_signatures(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
@@ -466,6 +470,7 @@ class Script:
|
|||||||
return [classes.Signature(self._inference_state, signature, call_details)
|
return [classes.Signature(self._inference_state, signature, call_details)
|
||||||
for signature in definitions.get_signatures()]
|
for signature in definitions.get_signatures()]
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def get_context(self, line=None, column=None):
|
def get_context(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
@@ -502,6 +507,8 @@ class Script:
|
|||||||
tree_name = name.tree_name
|
tree_name = name.tree_name
|
||||||
if tree_name is not None: # Happens with lambdas.
|
if tree_name is not None: # Happens with lambdas.
|
||||||
scope = tree_name.get_definition()
|
scope = tree_name.get_definition()
|
||||||
|
if scope.parent.type in ('async_stmt', 'async_funcdef'):
|
||||||
|
scope = scope.parent
|
||||||
if scope.start_pos[1] < column:
|
if scope.start_pos[1] < column:
|
||||||
break
|
break
|
||||||
definition = definition.parent()
|
definition = definition.parent()
|
||||||
@@ -541,6 +548,7 @@ class Script:
|
|||||||
finally:
|
finally:
|
||||||
self._inference_state.is_analysis = False
|
self._inference_state.is_analysis = False
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def get_names(self, **kwargs):
|
def get_names(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns names defined in the current file.
|
Returns names defined in the current file.
|
||||||
@@ -579,6 +587,7 @@ class Script:
|
|||||||
]
|
]
|
||||||
return sorted(defs, key=lambda x: x.start_pos)
|
return sorted(defs, key=lambda x: x.start_pos)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def rename(self, line=None, column=None, *, new_name):
|
def rename(self, line=None, column=None, *, new_name):
|
||||||
"""
|
"""
|
||||||
Renames all references of the variable under the cursor.
|
Renames all references of the variable under the cursor.
|
||||||
@@ -591,6 +600,7 @@ class Script:
|
|||||||
definitions = self.get_references(line, column, include_builtins=False)
|
definitions = self.get_references(line, column, include_builtins=False)
|
||||||
return refactoring.rename(self._inference_state, definitions, new_name)
|
return refactoring.rename(self._inference_state, definitions, new_name)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None):
|
def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None):
|
||||||
"""
|
"""
|
||||||
@@ -630,6 +640,7 @@ class Script:
|
|||||||
new_name, (line, column), until_pos
|
new_name, (line, column), until_pos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def extract_function(self, line, column, *, new_name, until_line=None, until_column=None):
|
def extract_function(self, line, column, *, new_name, until_line=None, until_column=None):
|
||||||
"""
|
"""
|
||||||
@@ -677,6 +688,7 @@ class Script:
|
|||||||
new_name, (line, column), until_pos
|
new_name, (line, column), until_pos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def inline(self, line=None, column=None):
|
def inline(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
Inlines a variable under the cursor. This is basically the opposite of
|
Inlines a variable under the cursor. This is basically the opposite of
|
||||||
|
|||||||
+16
-3
@@ -28,7 +28,7 @@ from jedi.inference.gradual.conversion import convert_names, convert_values
|
|||||||
from jedi.inference.base_value import ValueSet, HasNoContext
|
from jedi.inference.base_value import ValueSet, HasNoContext
|
||||||
from jedi.api.keywords import KeywordName
|
from jedi.api.keywords import KeywordName
|
||||||
from jedi.api import completion_cache
|
from jedi.api import completion_cache
|
||||||
from jedi.api.helpers import filter_follow_imports
|
from jedi.api.helpers import filter_follow_imports, recursion_limit
|
||||||
|
|
||||||
|
|
||||||
def _sort_names_by_start_pos(names):
|
def _sort_names_by_start_pos(names):
|
||||||
@@ -262,6 +262,7 @@ class BaseName:
|
|||||||
return last_leaf.end_pos
|
return last_leaf.end_pos
|
||||||
return definition.end_pos
|
return definition.end_pos
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def docstring(self, raw=False, fast=True):
|
def docstring(self, raw=False, fast=True):
|
||||||
r"""
|
r"""
|
||||||
Return a document string for this completion object.
|
Return a document string for this completion object.
|
||||||
@@ -422,6 +423,7 @@ class BaseName:
|
|||||||
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
|
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
|
||||||
|
|
||||||
@debug.increase_indent_cm('goto on name')
|
@debug.increase_indent_cm('goto on name')
|
||||||
|
@recursion_limit
|
||||||
def goto(self, *, follow_imports=False, follow_builtin_imports=False,
|
def goto(self, *, follow_imports=False, follow_builtin_imports=False,
|
||||||
only_stubs=False, prefer_stubs=False):
|
only_stubs=False, prefer_stubs=False):
|
||||||
|
|
||||||
@@ -452,6 +454,7 @@ class BaseName:
|
|||||||
for n in names]
|
for n in names]
|
||||||
|
|
||||||
@debug.increase_indent_cm('infer on name')
|
@debug.increase_indent_cm('infer on name')
|
||||||
|
@recursion_limit
|
||||||
def infer(self, *, only_stubs=False, prefer_stubs=False):
|
def infer(self, *, only_stubs=False, prefer_stubs=False):
|
||||||
"""
|
"""
|
||||||
Like :meth:`.Script.infer`, it can be useful to understand which type
|
Like :meth:`.Script.infer`, it can be useful to understand which type
|
||||||
@@ -551,7 +554,7 @@ class BaseName:
|
|||||||
return ''.join(lines[start_index:index + after + 1])
|
return ''.join(lines[start_index:index + after + 1])
|
||||||
|
|
||||||
def _get_signatures(self, for_docstring=False):
|
def _get_signatures(self, for_docstring=False):
|
||||||
if self._name.api_type == 'property':
|
if self._name.api_type in ('property', 'param'):
|
||||||
return []
|
return []
|
||||||
if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
|
if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
|
||||||
# For docstrings we don't resolve signatures if they are simple
|
# For docstrings we don't resolve signatures if they are simple
|
||||||
@@ -567,6 +570,7 @@ class BaseName:
|
|||||||
names = convert_names([self._name], prefer_stubs=True)
|
names = convert_names([self._name], prefer_stubs=True)
|
||||||
return [sig for name in names for sig in name.infer().get_signatures()]
|
return [sig for name in names for sig in name.infer().get_signatures()]
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def get_signatures(self):
|
def get_signatures(self):
|
||||||
"""
|
"""
|
||||||
Returns all potential signatures for a function or a class. Multiple
|
Returns all potential signatures for a function or a class. Multiple
|
||||||
@@ -579,6 +583,7 @@ class BaseName:
|
|||||||
for s in self._get_signatures()
|
for s in self._get_signatures()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def execute(self):
|
def execute(self):
|
||||||
"""
|
"""
|
||||||
Uses type inference to "execute" this identifier and returns the
|
Uses type inference to "execute" this identifier and returns the
|
||||||
@@ -588,6 +593,7 @@ class BaseName:
|
|||||||
"""
|
"""
|
||||||
return _values_to_definitions(self._name.infer().execute_with_values())
|
return _values_to_definitions(self._name.infer().execute_with_values())
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def get_type_hint(self):
|
def get_type_hint(self):
|
||||||
"""
|
"""
|
||||||
Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
|
Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
|
||||||
@@ -670,6 +676,7 @@ class Completion(BaseName):
|
|||||||
"""
|
"""
|
||||||
return self._complete(False)
|
return self._complete(False)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def docstring(self, raw=False, fast=True):
|
def docstring(self, raw=False, fast=True):
|
||||||
"""
|
"""
|
||||||
Documented under :meth:`BaseName.docstring`.
|
Documented under :meth:`BaseName.docstring`.
|
||||||
@@ -752,6 +759,7 @@ class Name(BaseName):
|
|||||||
super().__init__(inference_state, definition)
|
super().__init__(inference_state, definition)
|
||||||
|
|
||||||
@memoize_method
|
@memoize_method
|
||||||
|
@recursion_limit
|
||||||
def defined_names(self):
|
def defined_names(self):
|
||||||
"""
|
"""
|
||||||
List sub-definitions (e.g., methods in class).
|
List sub-definitions (e.g., methods in class).
|
||||||
@@ -797,6 +805,7 @@ class BaseSignature(Name):
|
|||||||
self._signature = signature
|
self._signature = signature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@recursion_limit
|
||||||
def params(self):
|
def params(self):
|
||||||
"""
|
"""
|
||||||
Returns definitions for all parameters that a signature defines.
|
Returns definitions for all parameters that a signature defines.
|
||||||
@@ -807,6 +816,7 @@ class BaseSignature(Name):
|
|||||||
return [ParamName(self._inference_state, n)
|
return [ParamName(self._inference_state, n)
|
||||||
for n in self._signature.get_param_names(resolve_stars=True)]
|
for n in self._signature.get_param_names(resolve_stars=True)]
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
"""
|
"""
|
||||||
Returns a text representation of the signature. This could for example
|
Returns a text representation of the signature. This could for example
|
||||||
@@ -831,7 +841,7 @@ class Signature(BaseSignature):
|
|||||||
def index(self):
|
def index(self):
|
||||||
"""
|
"""
|
||||||
Returns the param index of the current cursor position.
|
Returns the param index of the current cursor position.
|
||||||
Returns None if the index cannot be found in the curent call.
|
Returns None if the index cannot be found in the current call.
|
||||||
|
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
@@ -858,6 +868,7 @@ class Signature(BaseSignature):
|
|||||||
|
|
||||||
|
|
||||||
class ParamName(Name):
|
class ParamName(Name):
|
||||||
|
@recursion_limit
|
||||||
def infer_default(self):
|
def infer_default(self):
|
||||||
"""
|
"""
|
||||||
Returns default values like the ``1`` of ``def foo(x=1):``.
|
Returns default values like the ``1`` of ``def foo(x=1):``.
|
||||||
@@ -866,6 +877,7 @@ class ParamName(Name):
|
|||||||
"""
|
"""
|
||||||
return _values_to_definitions(self._name.infer_default())
|
return _values_to_definitions(self._name.infer_default())
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def infer_annotation(self, **kwargs):
|
def infer_annotation(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param execute_annotation: Default True; If False, values are not
|
:param execute_annotation: Default True; If False, values are not
|
||||||
@@ -874,6 +886,7 @@ class ParamName(Name):
|
|||||||
"""
|
"""
|
||||||
return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))
|
return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
"""
|
"""
|
||||||
Returns a simple representation of a param, like
|
Returns a simple representation of a param, like
|
||||||
|
|||||||
+51
-4
@@ -2,11 +2,14 @@
|
|||||||
Helpers for the API
|
Helpers for the API
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from contextlib import contextmanager
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from inspect import Parameter
|
from inspect import Parameter
|
||||||
|
from typing import Callable, TypeVar, Any, ParamSpec
|
||||||
|
|
||||||
from parso.python.parser import Parser
|
from parso.python.parser import Parser
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
@@ -18,9 +21,53 @@ from jedi.inference.compiled import get_string_value_set
|
|||||||
from jedi.cache import signature_time_cache, memoize_method
|
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
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
P = ParamSpec("P")
|
||||||
|
|
||||||
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
||||||
|
|
||||||
|
RECURSION_LIMIT = 3000
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def higher_recursion_limit():
|
||||||
|
"""Jedi needs a higher recursion limit for parsing and inference. Raise it
|
||||||
|
only temporarily, so importing Jedi does not change global interpreter
|
||||||
|
state and a user-defined higher limit is never lowered."""
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
if old_limit >= RECURSION_LIMIT:
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
sys.setrecursionlimit(RECURSION_LIMIT)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
def recursion_limit(func: Callable[P, T]) -> Callable[P, T]:
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with higher_recursion_limit():
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def recursion_limit_generator(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with higher_recursion_limit():
|
||||||
|
iterator = iter(func(*args, **kwargs))
|
||||||
|
while True:
|
||||||
|
with higher_recursion_limit():
|
||||||
|
try:
|
||||||
|
value = next(iterator)
|
||||||
|
except StopIteration:
|
||||||
|
return
|
||||||
|
# Restore the caller's limit before yielding a result.
|
||||||
|
yield value
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def _start_match(string, like_name):
|
def _start_match(string, like_name):
|
||||||
return string.startswith(like_name)
|
return string.startswith(like_name)
|
||||||
@@ -465,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)
|
@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
|
line = max(len(self._code_lines), 1) if line is None else line
|
||||||
if not (0 < line <= len(self._code_lines)):
|
if not (0 < line <= len(self._code_lines)):
|
||||||
raise ValueError('`line` parameter is not in a valid range.')
|
raise ValueError('`line` parameter is not in a valid range.')
|
||||||
@@ -484,8 +531,8 @@ def validate_line_column(func):
|
|||||||
raise ValueError('`column` parameter (%d) is not in a valid range '
|
raise ValueError('`column` parameter (%d) is not in a valid range '
|
||||||
'(0-%d) for line %d (%r).' % (
|
'(0-%d) for line %d (%r).' % (
|
||||||
column, line_len, line, line_string))
|
column, line_len, line, line_string))
|
||||||
return func(self, line, column, *args, **kwargs)
|
return func(self, line, column, *args, **kwargs) # type: ignore[arg-type]
|
||||||
return wrapper
|
return wrapper # type: ignore[return-value]
|
||||||
|
|
||||||
|
|
||||||
def get_module_names(module, all_scopes, definitions=True, references=False):
|
def get_module_names(module, all_scopes, definitions=True, references=False):
|
||||||
|
|||||||
+19
-2
@@ -15,7 +15,7 @@ from jedi import debug
|
|||||||
from jedi.api.environment import get_cached_default_environment, create_environment
|
from jedi.api.environment import get_cached_default_environment, create_environment
|
||||||
from jedi.api.exceptions import WrongVersion
|
from jedi.api.exceptions import WrongVersion
|
||||||
from jedi.api.completion import search_in_module
|
from jedi.api.completion import search_in_module
|
||||||
from jedi.api.helpers import split_search_string, get_module_names
|
from jedi.api.helpers import split_search_string, get_module_names, recursion_limit_generator
|
||||||
from jedi.inference.imports import load_module_from_path, \
|
from jedi.inference.imports import load_module_from_path, \
|
||||||
load_namespace_from_path, iter_module_names
|
load_namespace_from_path, iter_module_names
|
||||||
from jedi.inference.sys_path import discover_buildout_paths
|
from jedi.inference.sys_path import discover_buildout_paths
|
||||||
@@ -86,6 +86,14 @@ class Project:
|
|||||||
version, data = json.load(f)
|
version, data = json.load(f)
|
||||||
|
|
||||||
if version == 1:
|
if version == 1:
|
||||||
|
# A code base must not be able to enable the loading of its own
|
||||||
|
# extensions or point Jedi at a binary to execute, see the docs
|
||||||
|
# about security. The project file can be part of a checked out
|
||||||
|
# repository, so these settings are ignored for loaded projects.
|
||||||
|
if data.pop('load_unsafe_extensions', False):
|
||||||
|
debug.warning('load_unsafe_extensions is ignored for loaded projects')
|
||||||
|
if data.pop('environment_path', None) is not None:
|
||||||
|
debug.warning('environment_path is ignored for loaded projects')
|
||||||
return cls(**data)
|
return cls(**data)
|
||||||
else:
|
else:
|
||||||
raise WrongVersion(
|
raise WrongVersion(
|
||||||
@@ -99,6 +107,11 @@ class Project:
|
|||||||
data = dict(self.__dict__)
|
data = dict(self.__dict__)
|
||||||
data.pop('_environment', None)
|
data.pop('_environment', None)
|
||||||
data.pop('_django', None) # TODO make django setting public?
|
data.pop('_django', None) # TODO make django setting public?
|
||||||
|
|
||||||
|
# These are dropped anyway
|
||||||
|
data.pop('_load_unsafe_extensions', None)
|
||||||
|
data.pop('_environment_path', None)
|
||||||
|
|
||||||
data = {k.lstrip('_'): v for k, v in data.items()}
|
data = {k.lstrip('_'): v for k, v in data.items()}
|
||||||
data['path'] = str(data['path'])
|
data['path'] = str(data['path'])
|
||||||
|
|
||||||
@@ -242,7 +255,10 @@ class Project:
|
|||||||
def get_environment(self):
|
def get_environment(self):
|
||||||
if self._environment is None:
|
if self._environment is None:
|
||||||
if self._environment_path is not None:
|
if self._environment_path is not None:
|
||||||
self._environment = create_environment(self._environment_path, safe=False)
|
# environment_path can only be set by the user directly, it is
|
||||||
|
# never loaded from a project file, so it is trusted.
|
||||||
|
self._environment = create_environment(
|
||||||
|
self._environment_path, safe=False)
|
||||||
else:
|
else:
|
||||||
self._environment = get_cached_default_environment()
|
self._environment = get_cached_default_environment()
|
||||||
return self._environment
|
return self._environment
|
||||||
@@ -282,6 +298,7 @@ class Project:
|
|||||||
"""
|
"""
|
||||||
return self._search_func(string, complete=True, **kwargs)
|
return self._search_func(string, complete=True, **kwargs)
|
||||||
|
|
||||||
|
@recursion_limit_generator
|
||||||
@_try_to_skip_duplicates
|
@_try_to_skip_duplicates
|
||||||
def _search_func(self, string, complete=False, all_scopes=False):
|
def _search_func(self, string, complete=False, all_scopes=False):
|
||||||
# Using a Script is they easiest way to get an empty module context.
|
# Using a Script is they easiest way to get an empty module context.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
To use Jedi completion in Python interpreter, add the following in your shell
|
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
|
setup (e.g., ``.bashrc``). Jedi configures the classic readline shell and
|
||||||
not available on Windows. If you still want Jedi autocompletion in your REPL,
|
Python 3.13+'s new ``_pyrepl`` shell when those hooks are available.
|
||||||
just use IPython instead::
|
You might also want to try IPython instead, which also uses Jedi::
|
||||||
|
|
||||||
export PYTHONSTARTUP="$(python -m jedi repl)"
|
export PYTHONSTARTUP="$(python -m jedi repl)"
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class InferenceState:
|
|||||||
debug.dbg('execute result: %s in %s', value_set, value)
|
debug.dbg('execute result: %s in %s', value_set, value)
|
||||||
return value_set
|
return value_set
|
||||||
|
|
||||||
# mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362)
|
# mypy doesn't support decorated properties (https://github.com/python/mypy/issues/1362)
|
||||||
@property
|
@property
|
||||||
@inference_state_function_cache()
|
@inference_state_function_cache()
|
||||||
def builtins_module(self):
|
def builtins_module(self):
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ def _check_for_setattr(instance):
|
|||||||
|
|
||||||
def add_attribute_error(name_context, lookup_value, name):
|
def add_attribute_error(name_context, lookup_value, name):
|
||||||
message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name))
|
message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name))
|
||||||
# Check for __getattr__/__getattribute__ existance and issue a warning
|
# Check for __getattr__/__getattribute__ existence and issue a warning
|
||||||
# instead of an error, if that happens.
|
# instead of an error, if that happens.
|
||||||
typ = Error
|
typ = Error
|
||||||
if lookup_value.is_instance() and not lookup_value.is_compiled():
|
if lookup_value.is_instance() and not lookup_value.is_compiled():
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class Value(HelperValueMixin):
|
|||||||
def is_namespace(self):
|
def is_namespace(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_compiled(self):
|
def is_compiled(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_bound_method(self):
|
def is_bound_method(self):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import re
|
|||||||
import builtins
|
import builtins
|
||||||
import typing
|
import typing
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple, Any
|
||||||
|
|
||||||
from jedi.inference.compiled.getattr_static import getattr_static
|
from jedi.inference.compiled.getattr_static import getattr_static
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
|
|||||||
_sentinel = object()
|
_sentinel = object()
|
||||||
|
|
||||||
# Maps Python syntax to the operator module.
|
# Maps Python syntax to the operator module.
|
||||||
COMPARISON_OPERATORS = {
|
COMPARISON_OPERATORS: dict[str, Any] = {
|
||||||
'==': op.eq,
|
'==': op.eq,
|
||||||
'!=': op.ne,
|
'!=': op.ne,
|
||||||
'is': op.is_,
|
'is': op.is_,
|
||||||
|
|||||||
@@ -256,12 +256,11 @@ class CompiledValue(Value):
|
|||||||
arg.execute_annotation(context)
|
arg.execute_annotation(context)
|
||||||
for arg in arguments)
|
for arg in arguments)
|
||||||
elif name:
|
elif name:
|
||||||
# While with_generics only exists on very specific objects, we
|
# These are typically typing module objects. Try to use that to create generics.
|
||||||
# should probably be fine, because we control all the typing
|
|
||||||
# objects.
|
|
||||||
return ValueSet([
|
return ValueSet([
|
||||||
v.with_generics(arguments)
|
v.with_generics(arguments)
|
||||||
for v in self.inference_state.typing_module.py__getattribute__(name)
|
for v in self.inference_state.typing_module.py__getattribute__(name)
|
||||||
|
if hasattr(v, "with_generics")
|
||||||
]).execute_annotation(context)
|
]).execute_annotation(context)
|
||||||
return super().execute_annotation(context)
|
return super().execute_annotation(context)
|
||||||
|
|
||||||
|
|||||||
@@ -416,7 +416,15 @@ def import_module(inference_state, import_names, parent_module_value, sys_path):
|
|||||||
if is_pkg is None:
|
if is_pkg is None:
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
else:
|
else:
|
||||||
paths = parent_module_value.py__path__()
|
try:
|
||||||
|
py__path__ = parent_module_value.py__path__
|
||||||
|
except AttributeError:
|
||||||
|
# Due to meta_path, the value can be pretty much any compiled value
|
||||||
|
# and we're not guaranteed that the attribute is available, see
|
||||||
|
# also https://github.com/davidhalter/jedi/issues/2118
|
||||||
|
return NO_VALUES
|
||||||
|
else:
|
||||||
|
paths = py__path__()
|
||||||
if paths is None:
|
if paths is None:
|
||||||
# The module might not be a package.
|
# The module might not be a package.
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ class AbstractTreeName(AbstractNameDefinition):
|
|||||||
trailer = par.parent
|
trailer = par.parent
|
||||||
if trailer.type == 'arglist':
|
if trailer.type == 'arglist':
|
||||||
trailer = trailer.parent
|
trailer = trailer.parent
|
||||||
|
if trailer.type == 'error_node':
|
||||||
|
return []
|
||||||
if trailer.type != 'classdef':
|
if trailer.type != 'classdef':
|
||||||
if trailer.type == 'decorator':
|
if trailer.type == 'decorator':
|
||||||
value_set = context.infer_node(trailer.children[1])
|
value_set = context.infer_node(trailer.children[1])
|
||||||
|
|||||||
@@ -254,4 +254,4 @@ def _error_argument_count(funcdef, actual_count):
|
|||||||
else:
|
else:
|
||||||
before = 'from %s to ' % (len(params) - default_arguments)
|
before = 'from %s to ' % (len(params) - default_arguments)
|
||||||
return ('TypeError: %s() takes %s%s arguments (%s given).'
|
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).
|
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
|
||||||
|
|
||||||
|
|
||||||
@@ -93,8 +94,24 @@ def _remove_given_params(arguments, param_names):
|
|||||||
yield p
|
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
|
@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,13 +129,21 @@ 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:
|
||||||
|
if not is_recursion():
|
||||||
arg_callables = _iter_nodes_for_param(p)
|
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:
|
||||||
|
if not is_recursion():
|
||||||
kwarg_callables = list(_iter_nodes_for_param(p))
|
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:
|
||||||
@@ -157,7 +182,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 +215,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:
|
||||||
@@ -206,12 +231,3 @@ def process_params(param_names, star_count=3): # default means both * and **
|
|||||||
yield original_kwarg_name
|
yield original_kwarg_name
|
||||||
elif kwarg_names:
|
elif kwarg_names:
|
||||||
yield kwarg_names[0]
|
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 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
|
||||||
|
|||||||
@@ -435,6 +435,10 @@ def _find_overload_functions(context, tree_node):
|
|||||||
if dotted_name.type == 'name' and dotted_name.value == 'overload':
|
if dotted_name.type == 'name' and dotted_name.value == 'overload':
|
||||||
# TODO check with values if it's the right overload
|
# TODO check with values if it's the right overload
|
||||||
return True
|
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
|
return False
|
||||||
|
|
||||||
if tree_node.type == 'lambdef':
|
if tree_node.type == 'lambdef':
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class _BaseTreeInstance(AbstractInstanceValue):
|
|||||||
elif isinstance(f, CompiledValueFilter):
|
elif isinstance(f, CompiledValueFilter):
|
||||||
yield CompiledInstanceClassFilter(self, f)
|
yield CompiledInstanceClassFilter(self, f)
|
||||||
else:
|
else:
|
||||||
# Propably from the metaclass.
|
# Probably from the metaclass.
|
||||||
yield f
|
yield f
|
||||||
|
|
||||||
@inference_state_method_cache()
|
@inference_state_method_cache()
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import sys
|
|||||||
from typing import List
|
from typing import List
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jedi.inference.arguments import ValuesArguments
|
||||||
from jedi.inference.cache import inference_state_method_cache
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
|
from jedi.inference.gradual.annotation import infer_return_types
|
||||||
from jedi.inference.imports import goto_import, load_module_from_path
|
from jedi.inference.imports import goto_import, load_module_from_path
|
||||||
from jedi.inference.filters import ParserTreeFilter
|
from jedi.inference.filters import ParserTreeFilter
|
||||||
from jedi.inference.base_value import NO_VALUES, ValueSet
|
from jedi.inference.base_value import NO_VALUES, ValueSet
|
||||||
@@ -32,6 +34,19 @@ def execute(callback):
|
|||||||
def infer_anonymous_param(func):
|
def infer_anonymous_param(func):
|
||||||
def get_returns(value):
|
def get_returns(value):
|
||||||
if value.tree_node.annotation is not None:
|
if value.tree_node.annotation is not None:
|
||||||
|
if value.tree_node.parent.type == 'async_funcdef':
|
||||||
|
# Async fixtures provide the awaited value, rather than a
|
||||||
|
# coroutine. The annotation of an async generator describes
|
||||||
|
# the iterator, so the value is what that iterator yields.
|
||||||
|
result = infer_return_types(value, ValuesArguments([]))
|
||||||
|
if value.as_context().is_generator():
|
||||||
|
return ValueSet.from_sets(
|
||||||
|
lazy_value.infer()
|
||||||
|
for v in result
|
||||||
|
for lazy_value in v.iterate(is_async=True)
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
result = value.execute_with_values()
|
result = value.execute_with_values()
|
||||||
if any(v.name.get_qualified_names(include_module_names=True)
|
if any(v.name.get_qualified_names(include_module_names=True)
|
||||||
== ('typing', 'Generator')
|
== ('typing', 'Generator')
|
||||||
@@ -223,6 +238,8 @@ class FixtureFilter(ParserTreeFilter):
|
|||||||
if funcdef.type != "funcdef":
|
if funcdef.type != "funcdef":
|
||||||
return False
|
return False
|
||||||
decorated = funcdef.parent
|
decorated = funcdef.parent
|
||||||
|
if decorated.type == "async_funcdef":
|
||||||
|
decorated = decorated.parent
|
||||||
if decorated.type != "decorated":
|
if decorated.type != "decorated":
|
||||||
return False
|
return False
|
||||||
decorators = decorated.children[0]
|
decorators = decorated.children[0]
|
||||||
|
|||||||
+41
-11
@@ -100,17 +100,10 @@ def setup_readline(namespace_module=__main__, fuzzy=False):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
jedi_rl = JediRL()
|
||||||
# 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
|
def configure_readline(readline):
|
||||||
# 3.4 this is different, it always overwrites the completer if it's not
|
readline.set_completer(jedi_rl.complete)
|
||||||
# already imported here.
|
|
||||||
import rlcompleter # noqa: F401
|
|
||||||
import readline
|
|
||||||
except ImportError:
|
|
||||||
print("Jedi: Module readline not available.")
|
|
||||||
else:
|
|
||||||
readline.set_completer(JediRL().complete)
|
|
||||||
readline.parse_and_bind("tab: complete")
|
readline.parse_and_bind("tab: complete")
|
||||||
# jedi itself does the case matching
|
# jedi itself does the case matching
|
||||||
readline.parse_and_bind("set completion-ignore-case on")
|
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.
|
# No delimiters, Jedi handles that.
|
||||||
readline.set_completer_delims('')
|
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():
|
def version_info():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ setup(name='jedi',
|
|||||||
packages=find_packages(exclude=['test', 'test.*']),
|
packages=find_packages(exclude=['test', 'test.*']),
|
||||||
python_requires='>=3.10',
|
python_requires='>=3.10',
|
||||||
# Python 3.13 grammars are added to parso in 0.8.4
|
# Python 3.13 grammars are added to parso in 0.8.4
|
||||||
install_requires=['parso>=0.8.6,<0.9.0'],
|
install_requires=['parso>=0.8.7,<0.9.0'],
|
||||||
extras_require={
|
extras_require={
|
||||||
'dev': [
|
'dev': [
|
||||||
'pytest<9.0.0',
|
'pytest<9.0.0',
|
||||||
@@ -50,7 +50,7 @@ setup(name='jedi',
|
|||||||
'typing_extensions',
|
'typing_extensions',
|
||||||
# latest version on 2025-06-16
|
# latest version on 2025-06-16
|
||||||
'flake8==7.1.2',
|
'flake8==7.1.2',
|
||||||
'zuban==0.7.0',
|
'zuban==0.10.1',
|
||||||
# Arbitrary pins, latest at the time of pinning
|
# Arbitrary pins, latest at the time of pinning
|
||||||
'types-setuptools==80.9.0.20250529',
|
'types-setuptools==80.9.0.20250529',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -86,3 +86,28 @@ def broken_f1(value: Tuple[T]) -> Tuple[T]: ...
|
|||||||
tup: Tuple[float]
|
tup: Tuple[float]
|
||||||
#? float()
|
#? float()
|
||||||
broken_f1(broken_f1(tup))[0]
|
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("")
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Generator
|
from typing import AsyncGenerator, Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pytest import fixture
|
from pytest import fixture
|
||||||
@@ -208,3 +208,58 @@ def bla(admin_u
|
|||||||
def bla(admin_user):
|
def bla(admin_user):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# asynchronous fixtures
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def async_fixture():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module')
|
||||||
|
async def async_annotated_fixture() -> str:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
async def async_yield_fixture():
|
||||||
|
yield 1.0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def async_annotated_opaque() -> int:
|
||||||
|
return opaque_value
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def async_generator_annotated() -> AsyncGenerator[str, None]:
|
||||||
|
yield opaque_value
|
||||||
|
|
||||||
|
|
||||||
|
#? ['async_fixture']
|
||||||
|
def test_async_fixture_completion(async_fixt
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_fixture_inference(async_fixture, async_annotated_fixture, async_yield_fixture):
|
||||||
|
#? int()
|
||||||
|
async_fixture
|
||||||
|
#? str()
|
||||||
|
async_annotated_fixture
|
||||||
|
#? float()
|
||||||
|
async_yield_fixture
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_fixture_annotation(async_annotated_opaque, async_generator_annotated):
|
||||||
|
#? int()
|
||||||
|
async_annotated_opaque
|
||||||
|
#? str()
|
||||||
|
async_generator_annotated
|
||||||
|
|
||||||
|
|
||||||
|
#! 30 ['def async_fixture']
|
||||||
|
def test_async_fixture_goto(async_fixture):
|
||||||
|
pass
|
||||||
@@ -3,6 +3,8 @@ Test all things related to the ``jedi.api`` module.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -231,6 +233,19 @@ def test_goto_follow_imports(Script):
|
|||||||
assert d.name == 'a'
|
assert d.name == 'a'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('prefix', ['', 'def foo(bar): pass\n', 'def f():\n '])
|
||||||
|
@pytest.mark.parametrize('call', ['foo(bar=1', 'foo(bar=1,', 'foo(foo(bar=1'])
|
||||||
|
def test_goto_incomplete_named_argument(Script, prefix, call):
|
||||||
|
code = prefix + call
|
||||||
|
column = code.splitlines()[-1].index('bar')
|
||||||
|
assert Script(code).goto(column=column) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_goto_named_argument_in_complete_inner_call(Script):
|
||||||
|
param, = Script('def foo(bar): pass\nfoo(foo(bar=1)').goto(2, 8)
|
||||||
|
assert (param.name, param.line, param.column) == ('bar', 1, 8)
|
||||||
|
|
||||||
|
|
||||||
def test_goto_module(Script):
|
def test_goto_module(Script):
|
||||||
def check(line, expected, follow_imports=False):
|
def check(line, expected, follow_imports=False):
|
||||||
script = Script(path=path)
|
script = Script(path=path)
|
||||||
@@ -412,3 +427,90 @@ def test_infer_after_parentheses(Script, code, column, expected):
|
|||||||
assert completions == []
|
assert completions == []
|
||||||
else:
|
else:
|
||||||
assert [c.name for c in completions] == [expected]
|
assert [c.name for c in completions] == [expected]
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_does_not_change_recursion_limit():
|
||||||
|
# Regression test for #1925: importing jedi must not lower a higher
|
||||||
|
# user-defined limit, nor raise a lower one.
|
||||||
|
for limit in (1200, 5000):
|
||||||
|
code = ('import sys; sys.setrecursionlimit(%d); import jedi; '
|
||||||
|
'print(sys.getrecursionlimit())' % limit)
|
||||||
|
output = subprocess.check_output([sys.executable, '-c', code])
|
||||||
|
assert output.strip() == str(limit).encode()
|
||||||
|
|
||||||
|
|
||||||
|
def test_recursion_limit_is_scoped_and_restored():
|
||||||
|
from jedi.api.helpers import higher_recursion_limit, RECURSION_LIMIT
|
||||||
|
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
with higher_recursion_limit():
|
||||||
|
assert sys.getrecursionlimit() == RECURSION_LIMIT
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
sys.setrecursionlimit(5000)
|
||||||
|
with higher_recursion_limit():
|
||||||
|
assert sys.getrecursionlimit() == 5000
|
||||||
|
assert sys.getrecursionlimit() == 5000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
def test_deep_code_works_with_low_recursion_limit(Script):
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
code = 'x = ' + '(' * 200 + '1' + ')' * 200 + '\nx.'
|
||||||
|
assert Script(code).complete(2, 2)
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('method', [
|
||||||
|
'infer', 'get_type_hint', 'get_signatures', 'execute', 'defined_names',
|
||||||
|
])
|
||||||
|
def test_name_inference_recursion_limit(Script, method):
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
name = Script('x = ' + '(' * 200 + '1' + ')' * 200).get_names()[0]
|
||||||
|
getattr(name, method)()
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('method', ['search', 'complete_search'])
|
||||||
|
def test_project_search_recursion_limit(tmp_path, method):
|
||||||
|
from jedi import Project
|
||||||
|
|
||||||
|
(tmp_path / 'sample.py').write_text('x = ' + '(' * 200 + '1' + ')' * 200)
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
results = getattr(Project(tmp_path), method)('sample.x.real')
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
assert next(results).name == 'real'
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
results.close()
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
def test_recursion_limit_restored_after_error():
|
||||||
|
from jedi.api.helpers import recursion_limit
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
|
def fail():
|
||||||
|
raise ValueError('test')
|
||||||
|
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
with pytest.raises(ValueError, match='test'):
|
||||||
|
fail()
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
@@ -462,3 +462,21 @@ def test_module_completions(Script, module):
|
|||||||
|
|
||||||
def test_whitespace_at_end_after_dot(Script):
|
def test_whitespace_at_end_after_dot(Script):
|
||||||
assert 'strip' in [c.name for c in Script('str. ').complete()]
|
assert 'strip' in [c.name for c in Script('str. ').complete()]
|
||||||
|
|
||||||
|
|
||||||
|
def test_param_docstring_in_completion(Script):
|
||||||
|
# From Github #2063
|
||||||
|
code = '''\
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
def func(arg: Optional[int]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
func(arg'''
|
||||||
|
script = Script(code=code)
|
||||||
|
completions = script.complete(
|
||||||
|
line=len(code.splitlines()),
|
||||||
|
column=len(code.splitlines()[-1])
|
||||||
|
)
|
||||||
|
c, = completions
|
||||||
|
assert c.docstring() == ''
|
||||||
@@ -53,6 +53,21 @@ def x():
|
|||||||
]
|
]
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
async_code = '''\
|
||||||
|
async def coro():
|
||||||
|
return None
|
||||||
|
'''
|
||||||
|
async_method = '''\
|
||||||
|
class C:
|
||||||
|
async def coro(self):
|
||||||
|
return None
|
||||||
|
'''
|
||||||
|
async_nested = '''\
|
||||||
|
async def outer():
|
||||||
|
async def inner():
|
||||||
|
return None
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'code, line, column, full_name, expected_parents', [
|
'code, line, column, full_name, expected_parents', [
|
||||||
@@ -106,6 +121,21 @@ def x():
|
|||||||
(with_brackets, 3, None, 'myfile', []),
|
(with_brackets, 3, None, 'myfile', []),
|
||||||
(with_brackets, 4, 4, 'myfile.x', ['x']),
|
(with_brackets, 4, 4, 'myfile.x', ['x']),
|
||||||
(with_brackets, 4, 5, 'myfile.x', ['x']),
|
(with_brackets, 4, 5, 'myfile.x', ['x']),
|
||||||
|
|
||||||
|
(async_code, 2, 0, 'myfile', []),
|
||||||
|
(async_code, 2, 4, 'myfile.coro', ['coro']),
|
||||||
|
(async_code, 2, 5, 'myfile.coro', ['coro']),
|
||||||
|
(async_code, 2, None, 'myfile.coro', ['coro']),
|
||||||
|
('@decorator\n' + async_code, 3, 0, 'myfile', []),
|
||||||
|
('@decorator\n' + async_code, 3, 4, 'myfile.coro', ['coro']),
|
||||||
|
('@decorator\n' + async_code, 3, 5, 'myfile.coro', ['coro']),
|
||||||
|
(async_method, 3, 4, 'myfile.C', ['C']),
|
||||||
|
(async_method, 3, 8, 'myfile.C.coro', ['C', 'coro']),
|
||||||
|
(async_method, 3, 9, 'myfile.C.coro', ['C', 'coro']),
|
||||||
|
(async_nested, 3, 0, 'myfile', []),
|
||||||
|
(async_nested, 3, 4, 'myfile.outer', ['outer']),
|
||||||
|
(async_nested, 3, 8, None, ['outer', 'inner']),
|
||||||
|
(async_nested, 3, 9, None, ['outer', 'inner']),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_context(Script, code, line, column, full_name, expected_parents):
|
def test_context(Script, code, line, column, full_name, expected_parents):
|
||||||
|
|||||||
@@ -873,3 +873,20 @@ def test_whitespace_after_dot_completion(Script):
|
|||||||
# From #1954
|
# From #1954
|
||||||
completions = jedi.Interpreter("object. \n", []).complete(1, 8)
|
completions = jedi.Interpreter("object. \n", []).complete(1, 8)
|
||||||
assert "mro" in [c.name for c in completions]
|
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,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -54,6 +55,24 @@ def test_load_save_project(tmpdir):
|
|||||||
assert loaded.added_sys_path == ['/foo']
|
assert loaded.added_sys_path == ['/foo']
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_project_ignores_environment_path(tmpdir, monkeypatch):
|
||||||
|
# The project file can be part of a checked out repository, so it must not
|
||||||
|
# be able to point Jedi at a binary to execute.
|
||||||
|
def _get_subprocess(self):
|
||||||
|
raise RuntimeError('Should not get called!')
|
||||||
|
|
||||||
|
monkeypatch.setattr('jedi.api.environment.Environment._get_subprocess',
|
||||||
|
_get_subprocess)
|
||||||
|
|
||||||
|
Project(tmpdir.strpath, environment_path=sys.executable).save()
|
||||||
|
assert Project.load(tmpdir.strpath)._environment_path is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_project_ignores_unsafe_extensions(tmpdir):
|
||||||
|
Project(tmpdir.strpath, load_unsafe_extensions=True).save()
|
||||||
|
assert Project.load(tmpdir.strpath).load_unsafe_extensions is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'string, full_names, kwargs', [
|
'string, full_names, kwargs', [
|
||||||
('test_load_save_project', ['test_api.test_project.test_load_save_project'], {}),
|
('test_load_save_project', ['test_api.test_project.test_load_save_project'], {}),
|
||||||
|
|||||||
@@ -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)'
|
||||||
@@ -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(kw, kw)', '*args, a, b, c, **kwargs'),
|
||||||
('two_kwargs_redirects(simple, 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))',
|
('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, simple2)', 'a, b, /, *, x'),
|
||||||
('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'),
|
('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_lot_of_args(simple4, kw)', '*, b, c, **kwargs'),
|
||||||
|
|
||||||
('combined_redirect(combined_redirect(simple2, simple4), combined_redirect(kw, simple5))',
|
('combined_redirect(combined_redirect(simple2, simple4), combined_redirect(kw, simple5))',
|
||||||
'x, /, *, y'),
|
'*args, **kwargs'),
|
||||||
('combined_redirect(combined_redirect(simple4, simple2), combined_redirect(simple5, kw))',
|
('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))',
|
('combined_redirect(combined_redirect(a, kw), combined_redirect(kw, simple5))',
|
||||||
'a, b, /, *args, y'),
|
'*args, **kwargs'),
|
||||||
|
|
||||||
('no_redirect(kw)', '*args, **kwargs'),
|
('no_redirect(kw)', '*args, **kwargs'),
|
||||||
('no_redirect(akw)', '*args, **kwargs'),
|
('no_redirect(akw)', '*args, **kwargs'),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
|
import types
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -136,3 +137,29 @@ class TestSetupReadline(unittest.TestCase):
|
|||||||
|
|
||||||
def test_version_info():
|
def test_version_info():
|
||||||
assert utils.version_info()[:2] > (0, 7)
|
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