From 045b8a35a254a3e183f5672e1e8470b21e0a8fe0 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 26 Jan 2020 19:39:15 +0100 Subject: [PATCH 01/15] Remove dead code --- jedi/inference/arguments.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/jedi/inference/arguments.py b/jedi/inference/arguments.py index c9404478..c68dd469 100644 --- a/jedi/inference/arguments.py +++ b/jedi/inference/arguments.py @@ -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 From bec87f7ff82b0731713c6520a14c213341b4cecf Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 26 Jan 2020 20:07:25 +0100 Subject: [PATCH 02/15] Jedi understand now when you use del, fixes #313 --- test/completion/basic.py | 6 +++--- test/test_api/test_full_name.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/completion/basic.py b/test/completion/basic.py index b4006817..3ff919ca 100644 --- a/test/completion/basic.py +++ b/test/completion/basic.py @@ -209,11 +209,11 @@ if r: deleted_var = 3 del deleted_var -#? int() +#? deleted_var -#? ['deleted_var'] +#? [] deleted_var -#! ['deleted_var = 3'] +#! [] deleted_var # ----------------- diff --git a/test/test_api/test_full_name.py b/test/test_api/test_full_name.py index 4fdb861b..6858b6ca 100644 --- a/test/test_api/test_full_name.py +++ b/test/test_api/test_full_name.py @@ -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): From d630ed55f39728613f2a6c0b04124570d60aa8c4 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Tue, 28 Jan 2020 09:35:58 +0100 Subject: [PATCH 03/15] Avoid aborting search for yields when they are still reachable, see #683 --- jedi/inference/value/function.py | 22 +++++++++++----------- test/completion/generators.py | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/jedi/inference/value/function.py b/jedi/inference/value/function.py index fd3c2a04..ab9e65c7 100644 --- a/jedi/inference/value/function.py +++ b/jedi/inference/value/function.py @@ -201,15 +201,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 +218,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): diff --git a/test/completion/generators.py b/test/completion/generators.py index 1d5e5384..5ea3082e 100644 --- a/test/completion/generators.py +++ b/test/completion/generators.py @@ -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__ From e930f478611d86e29ed0353b0421ee1c861d81f1 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 29 Jan 2020 10:13:46 +0100 Subject: [PATCH 04/15] Make generators return more correct values with while loops, fixes #683 --- jedi/inference/lazy_value.py | 12 +++++++----- jedi/inference/syntax_tree.py | 5 ++++- jedi/inference/value/function.py | 2 +- test/completion/generators.py | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/jedi/inference/lazy_value.py b/jedi/inference/lazy_value.py index fa2f9609..44f8d596 100644 --- a/jedi/inference/lazy_value.py +++ b/jedi/inference/lazy_value.py @@ -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. diff --git a/jedi/inference/syntax_tree.py b/jedi/inference/syntax_tree.py index 6512fa76..6411fc81 100644 --- a/jedi/inference/syntax_tree.py +++ b/jedi/inference/syntax_tree.py @@ -797,7 +797,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: @@ -806,6 +807,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 diff --git a/jedi/inference/value/function.py b/jedi/inference/value/function.py index ab9e65c7..ae28ae9a 100644 --- a/jedi/inference/value/function.py +++ b/jedi/inference/value/function.py @@ -265,7 +265,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 diff --git a/test/completion/generators.py b/test/completion/generators.py index 5ea3082e..e36f3928 100644 --- a/test/completion/generators.py +++ b/test/completion/generators.py @@ -156,7 +156,7 @@ a, b = simple() #? int() str() a # For now this is ok. -#? +#? int() str() b From e1425de8a437100601f312840d57cc0d4a10e264 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 31 Jan 2020 13:26:56 +0100 Subject: [PATCH 05/15] Make sure to be able to deal with all kinds of loaders, fixes #1487 --- jedi/_compatibility.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index 709cd518..d8950018 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -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 From f4b1fc479d3e75b77ce8e8b3b4ac0e3bf57c0457 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 31 Jan 2020 13:38:27 +0100 Subject: [PATCH 06/15] Bump version to 0.16.1 --- CHANGELOG.rst | 3 +++ jedi/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index beabd71e..2d7bcccd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,9 @@ Changelog --------- +0.16.1 (2020--) ++++++++++++++++++++ + 0.16.0 (2020-01-26) +++++++++++++++++++ diff --git a/jedi/__init__.py b/jedi/__init__.py index efec776d..5ddb0d04 100644 --- a/jedi/__init__.py +++ b/jedi/__init__.py @@ -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 From 4c7179bc876990276f0007d451059dfa6518b75d Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 2 Feb 2020 16:55:10 +0100 Subject: [PATCH 07/15] Generate type hints, fixes #987 --- jedi/api/classes.py | 10 ++++++++++ jedi/inference/base_value.py | 23 ++++++++++++++++++++++ jedi/inference/compiled/value.py | 5 +++++ jedi/inference/gradual/base.py | 11 +++++++++++ jedi/inference/gradual/generics.py | 3 +++ jedi/inference/value/instance.py | 3 +++ jedi/inference/value/klass.py | 5 +++++ test/test_api/test_classes.py | 31 ++++++++++++++++++++++++++++++ 8 files changed, 91 insertions(+) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index b44e5eb8..b7aaadd3 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -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): """ diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index 96f43395..05134773 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -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([]) diff --git a/jedi/inference/compiled/value.py b/jedi/inference/compiled/value.py index fe8c03c2..bad5c73f 100644 --- a/jedi/inference/compiled/value.py +++ b/jedi/inference/compiled/value.py @@ -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', ""): + return 'None' + return None + class CompiledModule(CompiledValue): file_io = None # For modules diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index e09766be..d3472bdb 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -165,6 +165,14 @@ 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__() + 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 +247,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): """ diff --git a/jedi/inference/gradual/generics.py b/jedi/inference/gradual/generics.py index f0060e4f..6bd6aa63 100644 --- a/jedi/inference/gradual/generics.py +++ b/jedi/inference/gradual/generics.py @@ -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): diff --git a/jedi/inference/value/instance.py b/jedi/inference/value/instance.py index 858c778e..2eca9f9d 100644 --- a/jedi/inference/value/instance.py +++ b/jedi/inference/value/instance.py @@ -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) diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index b7d79646..37b743b3 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -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' diff --git a/test/test_api/test_classes.py b/test/test_api/test_classes.py index 4a17c572..b27c30e6 100644 --- a/test/test_api/test_classes.py +++ b/test/test_api/test_classes.py @@ -558,3 +558,34 @@ 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[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]]'), + ] +) +def test_get_type_hint(Script, code, expected, skip_python2): + code = 'from typing import *\n' + code + d, = Script(code).goto() + assert d.get_type_hint() == expected From e3c4b5b77e8ee46251e5748da9b76f766c93eade Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 2 Feb 2020 18:36:56 +0100 Subject: [PATCH 08/15] Make sure param hints are working for functions --- jedi/inference/gradual/base.py | 4 ++++ jedi/inference/value/function.py | 30 ++++++++++++++++++++++++++++++ test/test_api/test_classes.py | 11 ++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index d3472bdb..f9ccddb2 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -167,6 +167,10 @@ class GenericClass(ClassMixin, DefineGenericBase): 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: diff --git a/jedi/inference/value/function.py b/jedi/inference/value/function.py index ae28ae9a..44cdfd6c 100644 --- a/jedi/inference/value/function.py +++ b/jedi/inference/value/function.py @@ -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): diff --git a/test/test_api/test_classes.py b/test/test_api/test_classes.py index b27c30e6..a3b86981 100644 --- a/test/test_api/test_classes.py +++ b/test/test_api/test_classes.py @@ -573,6 +573,7 @@ def test_definition_goto_follow_imports(Script): ('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]'), @@ -583,9 +584,17 @@ def test_definition_goto_follow_imports(Script): ('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_python2): +def test_get_type_hint(Script, code, expected, skip_pre_python35): code = 'from typing import *\n' + code d, = Script(code).goto() assert d.get_type_hint() == expected From e802f5aabdcda5f899874ee7a91fb80dfe2de4e5 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 2 Feb 2020 23:28:55 +0100 Subject: [PATCH 09/15] Make sure to print errors in __main__ completions --- jedi/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jedi/__main__.py b/jedi/__main__.py index c1e58a62..14aa42cf 100644 --- a/jedi/__main__.py +++ b/jedi/__main__.py @@ -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() From eee919174d123065965bebc6fb8f1ecb2b683a8c Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 3 Feb 2020 00:58:54 +0100 Subject: [PATCH 10/15] Stubs should not become stubs again in the conversion function, fixes #1475 --- jedi/inference/gradual/conversion.py | 8 +++++++- test/test_api/test_completion.py | 5 +++-- test/test_inference/test_gradual/test_conversion.py | 8 ++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/jedi/inference/gradual/conversion.py b/jedi/inference/gradual/conversion.py index 074e29a7..541aa0d1 100644 --- a/jedi/inference/gradual/conversion.py +++ b/jedi/inference/gradual/conversion.py @@ -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 diff --git a/test/test_api/test_completion.py b/test/test_api/test_completion.py index 39ccb0da..7d5d183f 100644 --- a/test/test_api/test_completion.py +++ b/test/test_api/test_completion.py @@ -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() diff --git a/test/test_inference/test_gradual/test_conversion.py b/test/test_inference/test_gradual/test_conversion.py index 123858d6..b63129ae 100644 --- a/test/test_inference/test_gradual/test_conversion.py +++ b/test/test_inference/test_gradual/test_conversion.py @@ -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] From 3ad3dc08b8bf9d749b2d0d9fca038657711c82f2 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 3 Feb 2020 01:03:12 +0100 Subject: [PATCH 11/15] Run get_type_hint tests only for 3.6+ --- test/test_api/test_classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_api/test_classes.py b/test/test_api/test_classes.py index a3b86981..1d8b9ff4 100644 --- a/test/test_api/test_classes.py +++ b/test/test_api/test_classes.py @@ -594,7 +594,7 @@ def test_definition_goto_follow_imports(Script): 'foo(x: str, y: int=None) -> Union[int, str]'), ] ) -def test_get_type_hint(Script, code, expected, skip_pre_python35): +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 From a49c757b8ad70833aad231294ababa161cffe26c Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 3 Feb 2020 09:25:41 +0100 Subject: [PATCH 12/15] Make Ellipsis without list in Callable work, fixes #1475 --- jedi/inference/gradual/annotation.py | 14 +++++++------- test/completion/pep0484_typing.py | 6 ++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/jedi/inference/gradual/annotation.py b/jedi/inference/gradual/annotation.py index 4624f560..bda6c20b 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -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): diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index 9104ca11..4ebab2e9 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -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 # ------------------------- From 3388a9659bd6843ee051bea8efb33d226d497385 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 3 Feb 2020 22:27:22 +0100 Subject: [PATCH 13/15] Catch an error with illegal class instances, fixes #1491 --- jedi/inference/compiled/access.py | 4 +++- test/test_api/test_interpreter.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index f47ae773..f76a645f 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -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) diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 160639eb..1ec0b741 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -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): From 40fced2450c285e1eeb4f71da3f229f15e571b87 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Tue, 4 Feb 2020 19:34:42 +0100 Subject: [PATCH 14/15] Actually use follow_builtin_imports and improve the goto docstring, fixes #1492 --- jedi/api/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 44751109..426f56ec 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -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, From 7a55484b79bed3650a98a31a03a623f3fcd0680f Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Tue, 4 Feb 2020 23:56:01 +0100 Subject: [PATCH 15/15] Fix a test issue --- jedi/inference/gradual/annotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/inference/gradual/annotation.py b/jedi/inference/gradual/annotation.py index bda6c20b..1de87962 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -285,7 +285,7 @@ def infer_return_for_callable(arguments, param_values, result_values): all_type_vars.update(type_var_dict) return ValueSet.from_sets( - v.define_generics(type_var_dict) + v.define_generics(all_type_vars) if isinstance(v, (DefineGenericBase, TypeVar)) else ValueSet({v}) for v in result_values ).execute_annotation()