Merge branch 'master' into typeddict

This commit is contained in:
Dave Halter
2020-02-07 04:03:27 +01:00
27 changed files with 251 additions and 52 deletions

View File

@@ -3,6 +3,9 @@
Changelog
---------
0.16.1 (2020--)
+++++++++++++++++++
0.16.0 (2020-01-26)
+++++++++++++++++++

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -271,12 +271,12 @@ class Script(object):
"""
Return the first definition found, while optionally following imports.
Multiple objects may be returned, because Python itself is a
dynamic language, which means depending on an option you can have two
different versions of a function.
dynamic language, which means you can have two different versions of a
function.
:param follow_imports: The goto call will follow imports.
:param follow_builtin_imports: If follow_imports is True will decide if
it follow builtin imports.
:param follow_builtin_imports: If follow_imports is True will try to
look up names in builtins (i.e. compiled or extension modules).
:param only_stubs: Only return stubs for this goto call.
:param prefer_stubs: Prefer stubs to Python objects for this goto call.
:rtype: list of :class:`classes.Definition`
@@ -310,7 +310,7 @@ class Script(object):
names = list(name.goto())
if follow_imports:
names = helpers.filter_follow_imports(names)
names = helpers.filter_follow_imports(names, follow_builtin_imports)
names = convert_names(
names,
only_stubs=only_stubs,

View File

@@ -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):
"""

View File

@@ -131,15 +131,6 @@ def _parse_argument_clinic(string):
class _AbstractArgumentsMixin(object):
def infer_all(self, funcdef=None):
"""
Inferes all arguments as a support for static analysis
(normally Jedi).
"""
for key, lazy_value in self.unpack():
types = lazy_value.infer()
try_iter_content(types)
def unpack(self, funcdef=None):
raise NotImplementedError

View File

@@ -265,6 +265,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):
"""
@@ -415,6 +418,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([])

View File

@@ -570,4 +570,6 @@ def _is_class_instance(obj):
except AttributeError:
return False
else:
return cls != type and not issubclass(cls, NOT_CLASS_TYPES)
# The isinstance check for cls is just there so issubclass doesn't
# raise an exception.
return cls != type and isinstance(cls, type) and not issubclass(cls, NOT_CLASS_TYPES)

View File

@@ -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

View File

@@ -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(all_type_vars)
if isinstance(v, (DefineGenericBase, TypeVar)) else ValueSet({v})
for v in result_values
).execute_annotation()
def infer_type_vars_for_callable(arguments, lazy_params):

View File

@@ -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):
"""

View File

@@ -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

View File

@@ -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):

View File

@@ -3,8 +3,10 @@ from jedi.common.utils import monkeypatch
class AbstractLazyValue(object):
def __init__(self, data):
def __init__(self, data, min=1, max=1):
self.data = data
self.min = min
self.max = max
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.data)
@@ -26,16 +28,16 @@ class LazyKnownValues(AbstractLazyValue):
class LazyUnknownValue(AbstractLazyValue):
def __init__(self):
super(LazyUnknownValue, self).__init__(None)
def __init__(self, min=1, max=1):
super(LazyUnknownValue, self).__init__(None, min, max)
def infer(self):
return NO_VALUES
class LazyTreeValue(AbstractLazyValue):
def __init__(self, context, node):
super(LazyTreeValue, self).__init__(node)
def __init__(self, context, node, min=1, max=1):
super(LazyTreeValue, self).__init__(node, min, max)
self.context = context
# We need to save the predefined names. It's an unfortunate side effect
# that needs to be tracked otherwise results will be wrong.

View File

@@ -800,7 +800,8 @@ def check_tuple_assignments(name, value_set):
if isinstance(index, slice):
# For no star unpacking is not possible.
return NO_VALUES
for _ in range(index + 1):
i = 0
while i <= index:
try:
lazy_value = next(iterated)
except StopIteration:
@@ -809,6 +810,8 @@ def check_tuple_assignments(name, value_set):
# index number is high. Therefore break if the loop is
# finished.
return NO_VALUES
else:
i += lazy_value.max
value_set = lazy_value.infer()
return value_set

View File

@@ -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()
@@ -201,15 +228,15 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin):
returns = funcdef.iter_return_stmts()
for r in returns:
check = flow_analysis.reachability_check(self, funcdef, r)
if check is flow_analysis.UNREACHABLE:
debug.dbg('Return unreachable: %s', r)
if check_yields:
value_set |= ValueSet.from_sets(
lazy_value.infer()
for lazy_value in self._get_yield_lazy_value(r)
)
else:
if check_yields:
value_set |= ValueSet.from_sets(
lazy_value.infer()
for lazy_value in self._get_yield_lazy_value(r)
)
check = flow_analysis.reachability_check(self, funcdef, r)
if check is flow_analysis.UNREACHABLE:
debug.dbg('Return unreachable: %s', r)
else:
try:
children = r.children
@@ -218,9 +245,9 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin):
value_set |= ValueSet([ctx])
else:
value_set |= self.infer_node(children[1])
if check is flow_analysis.REACHABLE:
debug.dbg('Return reachable: %s', r)
break
if check is flow_analysis.REACHABLE:
debug.dbg('Return reachable: %s', r)
break
return value_set
def _get_yield_lazy_value(self, yield_expr):
@@ -265,7 +292,7 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin):
else:
types = self.get_return_values(check_yields=True)
if types:
yield LazyKnownValues(types)
yield LazyKnownValues(types, min=0, max=float('inf'))
return
last_for_stmt = for_stmt
@@ -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):

View File

@@ -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)

View File

@@ -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'

View File

@@ -209,11 +209,11 @@ if r:
deleted_var = 3
del deleted_var
#? int()
#?
deleted_var
#? ['deleted_var']
#? []
deleted_var
#! ['deleted_var = 3']
#! []
deleted_var
# -----------------

View File

@@ -78,6 +78,28 @@ g = iter([1.0])
#? float()
next(g)
x, y = Get()
#? int() str()
x
#? int() str()
x
class Iter:
def __iter__(self):
yield ""
i = 0
while True:
v = 1
yield v
i += 1
a, b, c = Iter()
#? str() int()
a
#? str() int()
b
#? str() int()
c
# -----------------
# __next__
@@ -134,7 +156,7 @@ a, b = simple()
#? int() str()
a
# For now this is ok.
#?
#? int() str()
b

View File

@@ -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
# -------------------------

View File

@@ -558,3 +558,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

View File

@@ -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()

View File

@@ -112,7 +112,8 @@ def test_os_path(Script):
def test_os_issues(Script):
"""Issue #873"""
assert [c.name for c in Script('import os\nos.nt''').complete()] == ['nt']
# nt is not found, because it's deleted
assert [c.name for c in Script('import os\nos.nt''').complete()] == []
def test_param_name(Script):

View File

@@ -530,6 +530,16 @@ def test__wrapped__():
assert c.line == syslogs_to_df.__wrapped__.__code__.co_firstlineno + 1
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
def test_illegal_class_instance():
class X:
__class__ = 1
X.__name__ = 'asdf'
d, = jedi.Interpreter('foo', [{'foo': X()}]).infer()
v, = d._name.infer()
assert not v.is_instance()
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
@pytest.mark.parametrize('module_name', ['sys', 'time', 'unittest.mock'])
def test_core_module_completes(module_name):

View File

@@ -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]