forked from VimPlug/jedi
Merge branch 'master' into project
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
Changelog
|
||||
---------
|
||||
|
||||
0.16.1 (2020--)
|
||||
+++++++++++++++++++
|
||||
|
||||
0.16.0 (2020-01-26)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ As you see Jedi is pretty simple and allows you to concentrate on writing a
|
||||
good text editor, while still having very good IDE features for Python.
|
||||
"""
|
||||
|
||||
__version__ = '0.16.0'
|
||||
__version__ = '0.16.1'
|
||||
|
||||
from jedi.api import Script, Interpreter, set_debug_function, \
|
||||
preload_module, names
|
||||
|
||||
@@ -48,7 +48,8 @@ def _complete():
|
||||
for c in jedi.Script(sys.argv[2]).complete():
|
||||
c.docstring()
|
||||
c.type
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pdb.post_mortem()
|
||||
|
||||
|
||||
|
||||
@@ -113,7 +113,12 @@ def find_module_py33(string, path=None, loader=None, full_name=None, is_global_s
|
||||
|
||||
|
||||
def _from_loader(loader, string):
|
||||
is_package = loader.is_package(string)
|
||||
try:
|
||||
is_package_method = loader.is_package
|
||||
except AttributeError:
|
||||
is_package = False
|
||||
else:
|
||||
is_package = is_package_method(string)
|
||||
try:
|
||||
get_filename = loader.get_filename
|
||||
except AttributeError:
|
||||
@@ -123,7 +128,11 @@ def _from_loader(loader, string):
|
||||
|
||||
# To avoid unicode and read bytes, "overwrite" loader.get_source if
|
||||
# possible.
|
||||
f = type(loader).get_source
|
||||
try:
|
||||
f = type(loader).get_source
|
||||
except AttributeError:
|
||||
raise ImportError("get_source was not defined on loader")
|
||||
|
||||
if is_py3 and f is not importlib.machinery.SourceFileLoader.get_source:
|
||||
# Unfortunately we are reading unicode here, not bytes.
|
||||
# It seems hard to get bytes, because the zip importer
|
||||
|
||||
@@ -513,6 +513,16 @@ class BaseDefinition(object):
|
||||
def execute(self):
|
||||
return _values_to_definitions(self._name.infer().execute_with_values())
|
||||
|
||||
def get_type_hint(self):
|
||||
"""
|
||||
Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
|
||||
|
||||
This method might be quite slow, especially for functions. The problem
|
||||
is finding executions for those functions to return something like
|
||||
``Callable[[int, str], str]``.
|
||||
"""
|
||||
return self._name.infer().get_type_hint()
|
||||
|
||||
|
||||
class Completion(BaseDefinition):
|
||||
"""
|
||||
|
||||
@@ -264,6 +264,9 @@ class Value(HelperValueMixin, BaseValue):
|
||||
def py__name__(self):
|
||||
return self.name.string_name
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
return None
|
||||
|
||||
|
||||
def iterate_values(values, contextualized_node=None, is_async=False):
|
||||
"""
|
||||
@@ -414,6 +417,26 @@ class ValueSet(BaseValueSet):
|
||||
def get_signatures(self):
|
||||
return [sig for c in self._set for sig in c.get_signatures()]
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
t = [v.get_type_hint(add_class_info=add_class_info) for v in self._set]
|
||||
type_hints = sorted(filter(None, t))
|
||||
if len(type_hints) == 1:
|
||||
return type_hints[0]
|
||||
|
||||
optional = 'None' in type_hints
|
||||
if optional:
|
||||
type_hints.remove('None')
|
||||
|
||||
if len(type_hints) == 0:
|
||||
return None
|
||||
elif len(type_hints) == 1:
|
||||
s = type_hints[0]
|
||||
else:
|
||||
s = 'Union[%s]' % ', '.join(type_hints)
|
||||
if optional:
|
||||
s = 'Optional[%s]' % s
|
||||
return s
|
||||
|
||||
|
||||
NO_VALUES = ValueSet([])
|
||||
|
||||
|
||||
@@ -285,6 +285,11 @@ class CompiledValue(Value):
|
||||
for k in self.access_handle.get_key_paths()
|
||||
]
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
if self.access_handle.get_repr() in ('None', "<class 'NoneType'>"):
|
||||
return 'None'
|
||||
return None
|
||||
|
||||
|
||||
class CompiledModule(CompiledValue):
|
||||
file_io = None # For modules
|
||||
|
||||
@@ -278,17 +278,17 @@ def infer_type_vars_for_execution(function, arguments, annotation_dict):
|
||||
|
||||
|
||||
def infer_return_for_callable(arguments, param_values, result_values):
|
||||
result = NO_VALUES
|
||||
all_type_vars = {}
|
||||
for pv in param_values:
|
||||
if pv.array_type == 'list':
|
||||
type_var_dict = infer_type_vars_for_callable(arguments, pv.py__iter__())
|
||||
all_type_vars.update(type_var_dict)
|
||||
|
||||
result |= ValueSet.from_sets(
|
||||
v.define_generics(type_var_dict)
|
||||
if isinstance(v, (DefineGenericBase, TypeVar)) else ValueSet({v})
|
||||
for v in result_values
|
||||
).execute_annotation()
|
||||
return result
|
||||
return ValueSet.from_sets(
|
||||
v.define_generics(type_var_dict)
|
||||
if isinstance(v, (DefineGenericBase, TypeVar)) else ValueSet({v})
|
||||
for v in result_values
|
||||
).execute_annotation()
|
||||
|
||||
|
||||
def infer_type_vars_for_callable(arguments, lazy_params):
|
||||
|
||||
@@ -165,6 +165,18 @@ class GenericClass(ClassMixin, DefineGenericBase):
|
||||
def _get_wrapped_value(self):
|
||||
return self._class_value
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
n = self.py__name__()
|
||||
# Not sure if this is the best way to do this, but all of these types
|
||||
# are a bit special in that they have type aliases and other ways to
|
||||
# become lower case. It's probably better to make them upper case,
|
||||
# because that's what you can use in annotations.
|
||||
n = dict(list="List", dict="Dict", set="Set", tuple="Tuple").get(n, n)
|
||||
s = n + self._generics_manager.get_type_hint()
|
||||
if add_class_info:
|
||||
return 'Type[%s]' % s
|
||||
return s
|
||||
|
||||
def get_type_var_filter(self):
|
||||
return _TypeVarFilter(self.get_generics(), self.list_type_vars())
|
||||
|
||||
@@ -239,6 +251,9 @@ class _GenericInstanceWrapper(ValueWrapper):
|
||||
return ValueSet([builtin_from_name(self.inference_state, u'None')])
|
||||
return self._wrapped_value.py__stop_iteration_returns()
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
return self._wrapped_value.class_value.get_type_hint(add_class_info=False)
|
||||
|
||||
|
||||
class _PseudoTreeNameClass(Value):
|
||||
"""
|
||||
|
||||
@@ -73,7 +73,13 @@ def _try_stub_to_python_names(names, prefer_stub_to_compiled=False):
|
||||
converted_names = converted.goto(name.get_public_name())
|
||||
if converted_names:
|
||||
for n in converted_names:
|
||||
yield n
|
||||
if n.get_root_context().is_stub():
|
||||
# If it's a stub again, it means we're going in
|
||||
# a circle. Probably some imports make it a
|
||||
# stub again.
|
||||
yield name
|
||||
else:
|
||||
yield n
|
||||
continue
|
||||
yield name
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ class _AbstractGenericManager(object):
|
||||
debug.warning('No param #%s found for annotation %s', index, self)
|
||||
return NO_VALUES
|
||||
|
||||
def get_type_hint(self):
|
||||
return '[%s]' % ', '.join(t.get_type_hint(add_class_info=False) for t in self.to_tuple())
|
||||
|
||||
|
||||
class LazyGenericManager(_AbstractGenericManager):
|
||||
def __init__(self, context_of_index, index_value):
|
||||
|
||||
@@ -86,6 +86,33 @@ class FunctionMixin(object):
|
||||
def py__name__(self):
|
||||
return self.name.string_name
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
return_annotation = self.tree_node.annotation
|
||||
if return_annotation is None:
|
||||
def param_name_to_str(n):
|
||||
s = n.string_name
|
||||
annotation = n.infer().get_type_hint()
|
||||
if annotation is not None:
|
||||
s += ': ' + annotation
|
||||
if n.default_node is not None:
|
||||
s += '=' + n.default_node.get_code(include_prefix=False)
|
||||
return s
|
||||
|
||||
function_execution = self.as_context()
|
||||
result = function_execution.infer()
|
||||
return_hint = result.get_type_hint()
|
||||
body = self.py__name__() + '(%s)' % ', '.join([
|
||||
param_name_to_str(n)
|
||||
for n in function_execution.get_param_names()
|
||||
])
|
||||
if return_hint is None:
|
||||
return body
|
||||
else:
|
||||
return_hint = return_annotation.get_code(include_prefix=False)
|
||||
body = self.py__name__() + self.tree_node.children[2].get_code(include_prefix=False)
|
||||
|
||||
return body + ' -> ' + return_hint
|
||||
|
||||
def py__call__(self, arguments):
|
||||
function_execution = self.as_context(arguments)
|
||||
return function_execution.infer()
|
||||
@@ -399,6 +426,9 @@ class OverloadedFunctionValue(FunctionMixin, ValueWrapper):
|
||||
def get_signature_functions(self):
|
||||
return self._overloaded_functions
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
return 'Union[%s]' % ', '.join(f.get_type_hint() for f in self._overloaded_functions)
|
||||
|
||||
|
||||
def _find_overload_functions(context, tree_node):
|
||||
def _is_overload_decorated(funcdef):
|
||||
|
||||
@@ -130,6 +130,9 @@ class AbstractInstanceValue(Value):
|
||||
for name in names
|
||||
)
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
return self.py__name__()
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (self.__class__.__name__, self.class_value)
|
||||
|
||||
|
||||
@@ -221,6 +221,11 @@ class ClassMixin(object):
|
||||
def _as_context(self):
|
||||
return ClassContext(self)
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
if add_class_info:
|
||||
return 'Type[%s]' % self.py__name__()
|
||||
return self.py__name__()
|
||||
|
||||
|
||||
class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)):
|
||||
api_type = u'class'
|
||||
|
||||
@@ -436,6 +436,12 @@ def the_callable() -> float: ...
|
||||
#? float()
|
||||
call3_pls()(the_callable)[0]
|
||||
|
||||
def call4_pls(fn: typing.Callable[..., TYPE_VARX]) -> typing.Callable[..., TYPE_VARX]:
|
||||
return ""
|
||||
|
||||
#? int()
|
||||
call4_pls(lambda x: 1)()
|
||||
|
||||
# -------------------------
|
||||
# TYPE_CHECKING
|
||||
# -------------------------
|
||||
|
||||
@@ -559,3 +559,43 @@ def test_definition_goto_follow_imports(Script):
|
||||
assert follow.description == 'def dumps'
|
||||
assert follow.line != 1
|
||||
assert follow.module_name == 'json'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, expected', [
|
||||
('1', 'int'),
|
||||
('x = None; x', 'None'),
|
||||
('n: Optional[str]; n', 'Optional[str]'),
|
||||
('n = None if xxxxx else ""; n', 'Optional[str]'),
|
||||
('n = None if xxxxx else str(); n', 'Optional[str]'),
|
||||
('n = None if xxxxx else str; n', 'Optional[Type[str]]'),
|
||||
('class Foo: pass\nFoo', 'Type[Foo]'),
|
||||
('class Foo: pass\nFoo()', 'Foo'),
|
||||
|
||||
('n: Type[List[int]]; n', 'Type[List[int]]'),
|
||||
('n: Type[List]; n', 'Type[list]'),
|
||||
('n: List; n', 'list'),
|
||||
('n: List[int]; n', 'List[int]'),
|
||||
('n: Iterable[int]; n', 'Iterable[int]'),
|
||||
|
||||
('n = [1]; n', 'List[int]'),
|
||||
('n = [1, ""]; n', 'List[Union[int, str]]'),
|
||||
('n = [1, str(), None]; n', 'List[Optional[Union[int, str]]]'),
|
||||
('n = {1, str()}; n', 'Set[Union[int, str]]'),
|
||||
('n = (1,); n', 'Tuple[int]'),
|
||||
('n = {1: ""}; n', 'Dict[int, str]'),
|
||||
('n = {1: "", 1.0: b""}; n', 'Dict[Union[float, int], Union[bytes, str]]'),
|
||||
|
||||
('n = next; n', 'Union[next(__i: Iterator[_T]) -> _T, '
|
||||
'next(__i: Iterator[_T], default: _VT) -> Union[_T, _VT]]'),
|
||||
('abs', 'abs(__n: SupportsAbs[_T]) -> _T'),
|
||||
('def foo(x, y): return x if xxxx else y\nfoo(str(), 1)\nfoo',
|
||||
'foo(x: str, y: int) -> Union[int, str]'),
|
||||
('def foo(x, y = None): return x if xxxx else y\nfoo(str(), 1)\nfoo',
|
||||
'foo(x: str, y: int=None) -> Union[int, str]'),
|
||||
]
|
||||
)
|
||||
def test_get_type_hint(Script, code, expected, skip_pre_python36):
|
||||
code = 'from typing import *\n' + code
|
||||
d, = Script(code).goto()
|
||||
assert d.get_type_hint() == expected
|
||||
|
||||
@@ -431,8 +431,9 @@ def test_completion_cache(Script, module_injector):
|
||||
assert cls.docstring() == 'foo()\n\ndoc2'
|
||||
|
||||
|
||||
def test_typing_module_completions(Script):
|
||||
for c in Script('import typing; typing.').completions():
|
||||
@pytest.mark.parametrize('module', ['typing', 'os'])
|
||||
def test_module_completions(Script, module):
|
||||
for c in Script('import {module}; {module}.'.format(module=module)).completions():
|
||||
# Just make sure that there are no errors
|
||||
c.type
|
||||
c.docstring()
|
||||
|
||||
@@ -62,3 +62,11 @@ def test_goto_import(Script, skip_pre_python35):
|
||||
assert d.is_stub()
|
||||
d, = Script(code).goto()
|
||||
assert not d.is_stub()
|
||||
|
||||
|
||||
def test_os_stat_result(Script):
|
||||
d, = Script('import os; os.stat_result').goto()
|
||||
assert d.is_stub()
|
||||
n = d._name
|
||||
# This should not be a different stub name
|
||||
assert convert_names([n]) == [n]
|
||||
|
||||
Reference in New Issue
Block a user