From 9a3f41e63b694bc95ee11b6ed88b71c2f03e2ed2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 13 Aug 2019 07:57:00 -0700 Subject: [PATCH 01/30] Complete path after ~. Note this is mostly to discuss as if I understood one of your message on Twitter, this was not possible without fuzzy completion. I tried with just this patch and that works great. Note that unlike IPython that right now does : ~/ -> /Full/Path/to/user/home But with this patch this just complete things correctly without expanding the tab. And I think not expanding the tab is actually better. Anyway, open that to better understand the why you were waiting for fuzzy completion. --- jedi/api/file_name.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jedi/api/file_name.py b/jedi/api/file_name.py index 542b5957..6fbecace 100644 --- a/jedi/api/file_name.py +++ b/jedi/api/file_name.py @@ -37,6 +37,8 @@ def file_name_completions(evaluator, module_context, start_leaf, string, like_name_length = len(os.path.basename(string) + like_name) addition = _get_string_additions(module_context, start_leaf) + if string.startswith('~'): + string = os.path.expanduser(string) if addition is None: return string = addition + string From 5329f9509639140ce0ad2c562ea10cd70327754a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 23 Aug 2019 18:26:11 +0200 Subject: [PATCH 02/30] Attempt at a test of completion of filepath after ~. I'm not quite sure how this will behave on windows, and we can't really create a tempdir (as we don't want to mess with path on home. One possibility would be to mock/monkeypatch scandir, listdir and os.path.expanduser or set $HOME in env; but I'm quite unsure we want to go that route. --- test/test_api/test_completion.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/test_api/test_completion.py b/test/test_api/test_completion.py index 178daee9..fefef549 100644 --- a/test/test_api/test_completion.py +++ b/test/test_api/test_completion.py @@ -1,4 +1,5 @@ -from os.path import join, sep as s +from os.path import join, sep as s, expanduser +import os import sys from textwrap import dedent @@ -83,6 +84,14 @@ def test_loading_unicode_files_with_bad_global_charset(Script, monkeypatch, tmpd line=2, column=4, path=filename2) s.completions() +def test_complete_expanduser(Script): + possibilities = os.listdir(expanduser('~')) + non_dots = [p for p in possibilities if not p.startswith('.') and len(p) > 1] + item = non_dots[0] + line = "'~%s%s'" % (os.sep, item) + s = Script(line, line=1, column=len(line)-1) + assert item in [c.name for c in s.completions()] + def test_fake_subnodes(Script): """ From 8eb980db73d1e5576d1ff92e6da079f37f3f644c Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 26 Jan 2020 19:25:23 +0100 Subject: [PATCH 03/30] Create the basics to work with TypedDict in the future --- jedi/inference/base_value.py | 1 + jedi/inference/gradual/typing.py | 62 ++++++++++++++++++++++++++++++- jedi/inference/syntax_tree.py | 3 ++ jedi/inference/value/klass.py | 33 +++++++++++++++- jedi/parser_utils.py | 19 ++++++++++ test/completion/pep0484_typing.py | 41 ++++++++++++++++++++ 6 files changed, 156 insertions(+), 3 deletions(-) diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index 96f43395..3705d7bc 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -258,6 +258,7 @@ class Value(HelperValueMixin, BaseValue): def _as_context(self): raise NotImplementedError('Not all values need to be converted to contexts: %s', self) + @property def name(self): raise NotImplementedError diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 3b3cdf17..64e1e43c 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -81,7 +81,8 @@ class TypingModuleName(NameWrapper): elif name == 'TypedDict': # TODO doesn't even exist in typeshed/typing.py, yet. But will be # added soon. - pass + yield TypedDictBase.create_cached( + inference_state, self.parent_context, self.tree_name) elif name in ('no_type_check', 'no_type_check_decorator'): # This is not necessary, as long as we are not doing type checking. for c in self._wrapped_name.infer(): # Fuck my life Python 2 @@ -339,3 +340,62 @@ class CastFunction(BaseTypingValue): @repack_with_argument_clinic('type, object, /') def py__call__(self, type_value_set, object_value_set): return type_value_set.execute_annotation() + + +class TypedDictBase(BaseTypingValue): + """ + This class has no responsibilities and is just here to make sure that typed + dicts can be identified. + """ + + +class TypedDictClass(Value): + """ + This represents a class defined like: + + class Foo(TypedDict): + bar: str + """ + def __init__(self, definition_class): + super().__init__(definition_class.inference_state, definition_class.parent_context) + self.tree_node = definition_class.tree_node + self._definition_class = definition_class + + def get_filters(self, origin_scope=None): + """ + A TypedDict doesn't have attributes. + """ + o, = self.inference_state.builtins_module.py__getattribute__('object') + return o.get_filters() + + @property + def name(self): + return ValueName(self, self.tree_node.name) + + def py__call__(self, arguments): + return ValueSet({TypedDict(self._definition_class)}) + + +class TypedDict(LazyValueWrapper): + """Represents the instance version of ``TypedDictClass``.""" + def __init__(self, definition_class): + self.inference_state = definition_class.inference_state + self.parent_context = definition_class.parent_context + self.tree_node = definition_class.tree_node + self._definition_class = definition_class + + @property + def name(self): + return ValueName(self, self.tree_node.name) + + def py__simple_getitem__(self, index): + return ValueSet({self.inference_state.builtins_module}) + + def get_key_values(self): + from jedi.inference.compiled import create_simple_object + return ValueSet({create_simple_object(self.inference_state, 'baz')}) + + def _get_wrapped_value(self): + d, = self.inference_state.builtins_module.py__getattribute__('dict') + result, = d.execute_with_values() + return result diff --git a/jedi/inference/syntax_tree.py b/jedi/inference/syntax_tree.py index 6512fa76..51bc960a 100644 --- a/jedi/inference/syntax_tree.py +++ b/jedi/inference/syntax_tree.py @@ -26,6 +26,7 @@ from jedi.inference.compiled.access import COMPARISON_OPERATORS from jedi.inference.cache import inference_state_method_cache from jedi.inference.gradual.stub_value import VersionInfo from jedi.inference.gradual import annotation +from jedi.inference.gradual.typing import TypedDictClass from jedi.inference.names import TreeNameDefinition from jedi.inference.context import CompForContext from jedi.inference.value.decorator import Decoratee @@ -748,6 +749,8 @@ def _apply_decorators(context, node): parent_context=context, tree_node=node ) + if decoratee_value.is_typeddict(): + decoratee_value = TypedDictClass(decoratee_value) else: decoratee_value = FunctionValue.from_context(context, node) initial = values = ValueSet([decoratee_value]) diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index b7d79646..743c17cf 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -38,11 +38,11 @@ py__doc__() Returns the docstring for a value. """ from jedi import debug from jedi._compatibility import use_metaclass -from jedi.parser_utils import get_cached_parent_scope +from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \ inference_state_method_generator_cache from jedi.inference import compiled -from jedi.inference.lazy_value import LazyKnownValues +from jedi.inference.lazy_value import LazyKnownValues, LazyTreeValue from jedi.inference.filters import ParserTreeFilter from jedi.inference.names import TreeNameDefinition, ValueName from jedi.inference.arguments import unpack_arglist, ValuesArguments @@ -265,6 +265,35 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase self.inference_state.builtins_module.py__getattribute__('object') )] + def is_typeddict(self): + # TODO Do a proper mro resolution. Currently we are just listing + # classes. However, it's a complicated algorithm. + from jedi.inference.gradual.typing import TypedDictBase + for lazy_cls in self.py__bases__(): + if not isinstance(lazy_cls, LazyTreeValue): + return False + tree_node = lazy_cls.data + # Only resolve simple classes, stuff like Iterable[str] are more + # intensive to resolve and if generics are involved, we know it's + # not a TypedDict. + if not expr_is_dotted(tree_node): + return False + + for cls in lazy_cls.infer(): + if isinstance(cls, TypedDictBase): + return True + try: + method = cls.is_typeddict + except AttributeError: + # We're only dealing with simple classes, so just returning + # here should be fine. This only happens with e.g. compiled + # classes. + return False + else: + if method(): + return True + return False + def py__getitem__(self, index_value_set, contextualized_node): from jedi.inference.gradual.base import GenericClass if not index_value_set: diff --git a/jedi/parser_utils.py b/jedi/parser_utils.py index 0f7ba429..7f69a80c 100644 --- a/jedi/parser_utils.py +++ b/jedi/parser_utils.py @@ -293,6 +293,25 @@ def cut_value_at_position(leaf, position): return ''.join(lines) +def expr_is_dotted(node): + """ + Checks if a path looks like `name` or `name.foo.bar` and not `name()`. + """ + if node.type == 'atom': + if len(node.children) == 3 and node.children[0] == '(': + return expr_is_dotted(node.children[1]) + return False + if node.type == 'atom_expr': + children = node.children + if children[0] == 'await': + return False + if not expr_is_dotted(children[0]): + return False + # Check trailers + return all(c.children[0] == '.' for c in children[1:]) + return node.type == 'name' + + def _function_is_x_method(method_name): def wrapper(function_node): """ diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index 9104ca11..7a48aca9 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -493,3 +493,44 @@ def dynamic_annotation(x: int): #? int() dynamic_annotation('') + +# ------------------------- +# TypeDict +# ------------------------- + +# python >= 3.8 + +class Foo(typing.TypedDict): + foo: str + bar: List[int] + foo + #! ['foo: str'] + foo + #? str() + foo + +#! ['class Foo'] +d: Foo +#? str() +d['foo'] +#? str() +d['bar'][0] +#? +d['baz'] + +#? +d.foo +#? +d.bar +#! [] +d.foo + +#? [] +Foo.set +#? ['setdefault'] +d.setdefaul + +#? 5 ["'foo'"] +d['fo'] +#? 5 ['"bar"'] +d["bar"] From 87161df2f0a5d2fa21d310b65efe538ce6c8d8e7 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 7 Feb 2020 16:45:03 +0100 Subject: [PATCH 04/30] Make sure that typeddict py__getitem__ works --- jedi/inference/gradual/typing.py | 8 +++++++- jedi/inference/value/instance.py | 6 +++--- jedi/inference/value/klass.py | 9 +++++---- test/completion/pep0484_typing.py | 4 ++-- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 64e1e43c..2d7d18ef 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -389,7 +389,13 @@ class TypedDict(LazyValueWrapper): return ValueName(self, self.tree_node.name) def py__simple_getitem__(self, index): - return ValueSet({self.inference_state.builtins_module}) + if isinstance(index, str): + return ValueSet.from_sets( + name.infer() + for filter in self._definition_class.get_filters(is_instance=True) + for name in filter.get(index) + ) + return NO_VALUES def get_key_values(self): from jedi.inference.compiled import create_simple_object diff --git a/jedi/inference/value/instance.py b/jedi/inference/value/instance.py index 2eca9f9d..b884b14d 100644 --- a/jedi/inference/value/instance.py +++ b/jedi/inference/value/instance.py @@ -547,10 +547,10 @@ class InstanceClassFilter(AbstractFilter): self._class_filter = class_filter def get(self, name): - return self._convert(self._class_filter.get(name, from_instance=True)) + return self._convert(self._class_filter.get(name)) def values(self): - return self._convert(self._class_filter.values(from_instance=True)) + return self._convert(self._class_filter.values()) def _convert(self, names): return [ @@ -586,7 +586,7 @@ class SelfAttributeFilter(ClassFilter): if trailer.type == 'trailer' \ and len(trailer.parent.children) == 2 \ and trailer.children[0] == '.': - if name.is_definition() and self._access_possible(name, from_instance=True): + if name.is_definition() and self._access_possible(name): # TODO filter non-self assignments instead of this bad # filter. if self._is_in_right_scope(trailer.parent.children[0], name): diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index 764644d6..41c669d7 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -104,12 +104,12 @@ class ClassFilter(ParserTreeFilter): node = get_cached_parent_scope(self._used_names, node) return False - def _access_possible(self, name, from_instance=False): + def _access_possible(self, name): # Filter for ClassVar variables # TODO this is not properly done, yet. It just checks for the string # ClassVar in the annotation, which can be quite imprecise. If we # wanted to do this correct, we would have to infer the ClassVar. - if not from_instance: + if not self._is_instance: expr_stmt = name.get_definition() if expr_stmt is not None and expr_stmt.type == 'expr_stmt': annassign = expr_stmt.children[1] @@ -122,9 +122,9 @@ class ClassFilter(ParserTreeFilter): return not name.value.startswith('__') or name.value.endswith('__') \ or self._equals_origin_scope() - def _filter(self, names, from_instance=False): + def _filter(self, names): names = super(ClassFilter, self)._filter(names) - return [name for name in names if self._access_possible(name, from_instance)] + return [name for name in names if self._access_possible(name)] class ClassMixin(object): @@ -270,6 +270,7 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase self.inference_state.builtins_module.py__getattribute__('object') )] + @inference_state_method_cache(default=False) def is_typeddict(self): # TODO Do a proper mro resolution. Currently we are just listing # classes. However, it's a complicated algorithm. diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index 8dc03d96..eef0bf13 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -508,7 +508,7 @@ dynamic_annotation('') class Foo(typing.TypedDict): foo: str - bar: List[int] + bar: typing.List[float] foo #! ['foo: str'] foo @@ -519,7 +519,7 @@ class Foo(typing.TypedDict): d: Foo #? str() d['foo'] -#? str() +#? float() d['bar'][0] #? d['baz'] From 6a9745b42ba2365f08b4966ee82c9f0a61a48d97 Mon Sep 17 00:00:00 2001 From: Sam Roeca Date: Tue, 28 Jan 2020 14:07:05 -0500 Subject: [PATCH 05/30] Get basic completions working with TypedDict --- jedi/inference/gradual/typing.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 2d7d18ef..ffb9b99b 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -5,8 +5,9 @@ values. This file deals with all the typing.py cases. """ +import itertools from jedi import debug -from jedi.inference.compiled import builtin_from_name +from jedi.inference.compiled import builtin_from_name, create_simple_object from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \ LazyValueWrapper from jedi.inference.lazy_value import LazyKnownValues @@ -398,8 +399,14 @@ class TypedDict(LazyValueWrapper): return NO_VALUES def get_key_values(self): - from jedi.inference.compiled import create_simple_object - return ValueSet({create_simple_object(self.inference_state, 'baz')}) + filtered_values = itertools.chain.from_iterable(( + f.values(from_instance=True) + for f in self._definition_class.get_filters(is_instance=True) + )) + return ValueSet({ + create_simple_object(self.inference_state, v.string_name) + for v in filtered_values + }) def _get_wrapped_value(self): d, = self.inference_state.builtins_module.py__getattribute__('dict') From 9d2083fa08d207c9b9784e8f548cfadc3251d0c6 Mon Sep 17 00:00:00 2001 From: Sam Roeca Date: Fri, 7 Feb 2020 13:38:52 -0500 Subject: [PATCH 06/30] Remove argument to filter.values() Given 87161df2, values(from_instance=False) doesn't produce completions anymore. Therefore, we remove from_instance as an argument. --- jedi/inference/gradual/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index ffb9b99b..be353391 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -400,7 +400,7 @@ class TypedDict(LazyValueWrapper): def get_key_values(self): filtered_values = itertools.chain.from_iterable(( - f.values(from_instance=True) + f.values() for f in self._definition_class.get_filters(is_instance=True) )) return ValueSet({ From cf954bf0068b3e0b70d0962f87e8e1ac1a751de1 Mon Sep 17 00:00:00 2001 From: Sam Roeca Date: Fri, 7 Feb 2020 14:40:39 -0500 Subject: [PATCH 07/30] Expand on TypedDict tests. Adds a function that takes the TypedDict as an argument. Note: the last two tests are failing, along with lots of other tests throughout the system. --- test/completion/pep0484_typing.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index eef0bf13..e81a443f 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -509,11 +509,33 @@ dynamic_annotation('') class Foo(typing.TypedDict): foo: str bar: typing.List[float] + an_int: int foo #! ['foo: str'] foo #? str() foo + #? int() + an_int + +def typed_dict_test_foo(arg: Foo): + a_string = arg['foo'] + a_list_of_floats = arg['bar'] + an_int = arg['an_int'] + + #? str() + a_string + #? [float()] + a_list_of_strings + #? int() + an_int + + #? ['isupper'] + a_string.isuppe + #? ['pop'] + a_list_of_floats.po + #? ['as_integer_ratio'] + an_int.as_integer_rati #! ['class Foo'] d: Foo From 2cf06bcf48f4bd9072d7b74f3664124ccc7b46bb Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 8 Feb 2020 20:04:47 +0100 Subject: [PATCH 08/30] Make sure to mention that VSCode is using Jedi It has been used for a long time --- README.rst | 1 + docs/docs/usage.rst | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/README.rst b/README.rst index 0611bdc4..10314cd5 100644 --- a/README.rst +++ b/README.rst @@ -43,6 +43,7 @@ the CPython REPL you have to install it. Jedi can currently be used with the following editors/projects: - Vim (jedi-vim_, YouCompleteMe_, deoplete-jedi_, completor.vim_) +- VSCode (Jedi is the default backend) - Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_) - Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3]) - TextMate_ (Not sure if it's actually working) diff --git a/docs/docs/usage.rst b/docs/docs/usage.rst index bc30af35..9b1ccd50 100644 --- a/docs/docs/usage.rst +++ b/docs/docs/usage.rst @@ -23,6 +23,10 @@ Vim: - YouCompleteMe_ - deoplete-jedi_ +VSCode: + +- Jedi is used by default if you download VSCode. + Emacs: - Jedi.el_ From de2f7535465b11d185187875ef7b7db385945de4 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 8 Feb 2020 20:06:08 +0100 Subject: [PATCH 09/30] Revert "Make sure to mention that VSCode is using Jedi" It was already in there. This reverts commit 2cf06bcf48f4bd9072d7b74f3664124ccc7b46bb. --- README.rst | 1 - docs/docs/usage.rst | 4 ---- 2 files changed, 5 deletions(-) diff --git a/README.rst b/README.rst index 10314cd5..0611bdc4 100644 --- a/README.rst +++ b/README.rst @@ -43,7 +43,6 @@ the CPython REPL you have to install it. Jedi can currently be used with the following editors/projects: - Vim (jedi-vim_, YouCompleteMe_, deoplete-jedi_, completor.vim_) -- VSCode (Jedi is the default backend) - Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_) - Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3]) - TextMate_ (Not sure if it's actually working) diff --git a/docs/docs/usage.rst b/docs/docs/usage.rst index 9b1ccd50..bc30af35 100644 --- a/docs/docs/usage.rst +++ b/docs/docs/usage.rst @@ -23,10 +23,6 @@ Vim: - YouCompleteMe_ - deoplete-jedi_ -VSCode: - -- Jedi is used by default if you download VSCode. - Emacs: - Jedi.el_ From bcb7cc864c1a36d945a8fc572a8e014847f30318 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 8 Feb 2020 20:09:46 +0100 Subject: [PATCH 10/30] Make sure to move up VSCode, because it's used a lot --- README.rst | 2 +- docs/docs/usage.rst | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 0611bdc4..bf8f3c8c 100644 --- a/README.rst +++ b/README.rst @@ -43,6 +43,7 @@ the CPython REPL you have to install it. Jedi can currently be used with the following editors/projects: - Vim (jedi-vim_, YouCompleteMe_, deoplete-jedi_, completor.vim_) +- `Visual Studio Code`_ (via `Python Extension `_) - Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_) - Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3]) - TextMate_ (Not sure if it's actually working) @@ -50,7 +51,6 @@ Jedi can currently be used with the following editors/projects: `_] - Atom_ (autocomplete-python-jedi_) - `GNOME Builder`_ (with support for GObject Introspection) -- `Visual Studio Code`_ (via `Python Extension `_) - Gedit (gedi_) - wdb_ - Web Debugger - `Eric IDE`_ (Available as a plugin) diff --git a/docs/docs/usage.rst b/docs/docs/usage.rst index bc30af35..3ebbb752 100644 --- a/docs/docs/usage.rst +++ b/docs/docs/usage.rst @@ -23,6 +23,10 @@ Vim: - YouCompleteMe_ - deoplete-jedi_ +Visual Studio Code: + +- `Python Extension`_ + Emacs: - Jedi.el_ @@ -48,10 +52,6 @@ Kate: `__, you have to enable it, though. -Visual Studio Code: - -- `Python Extension`_ - Atom: - autocomplete-python-jedi_ @@ -114,4 +114,4 @@ Using a custom ``$HOME/.pythonrc.py`` .. _GNOME Builder: https://wiki.gnome.org/Apps/Builder/ .. _gedi: https://github.com/isamert/gedi .. _Eric IDE: https://eric-ide.python-projects.org -.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python +.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=ms-python.python From fd1f9f22e9fad1896fcb71d747a727dc0bc745a3 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sat, 8 Feb 2020 21:37:01 +0000 Subject: [PATCH 11/30] Update use of _source which no longer exists to _code --- sith.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sith.py b/sith.py index abbfca11..6bf39091 100755 --- a/sith.py +++ b/sith.py @@ -151,7 +151,7 @@ class TestCase(object): # Three lines ought to be enough lower = lineno - show if lineno - show > 0 else 0 prefix = ' |' - for i, line in enumerate(self.script._source.split('\n')[lower:lineno]): + for i, line in enumerate(self.script._code.split('\n')[lower:lineno]): print(prefix, lower + i + 1, line) print(prefix, ' ', ' ' * (column + len(str(lineno))), '^') From 370e539a7e9704f3c3214583bb0d716369530f46 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sat, 8 Feb 2020 21:37:23 +0000 Subject: [PATCH 12/30] Remove additional prefix which seems incorrect --- sith.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sith.py b/sith.py index 6bf39091..34823f59 100755 --- a/sith.py +++ b/sith.py @@ -153,7 +153,7 @@ class TestCase(object): prefix = ' |' for i, line in enumerate(self.script._code.split('\n')[lower:lineno]): print(prefix, lower + i + 1, line) - print(prefix, ' ', ' ' * (column + len(str(lineno))), '^') + print(prefix, ' ' * (column + len(str(lineno))), '^') def show_operation(self): print("%s:\n" % self.operation.capitalize()) From d42d3f45f08e06f3893e6e948c332cc0e52a2ca9 Mon Sep 17 00:00:00 2001 From: Jma353 Date: Mon, 10 Feb 2020 17:59:21 -0800 Subject: [PATCH 13/30] Add venv to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ada8cd6b..fc6b9070 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ jedi.egg-info/ record.json /.cache/ /.pytest_cache +/venv/ From ac47866c4cb8c425b99c4a30918236c90f3f2cce Mon Sep 17 00:00:00 2001 From: Sam Roeca Date: Tue, 11 Feb 2020 18:32:15 -0500 Subject: [PATCH 14/30] TypedDict: fix non-inheritance tests, add inheritance Note: tests currently failing --- test/completion/pep0484_typing.py | 43 ++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index e81a443f..6472880d 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -510,7 +510,6 @@ class Foo(typing.TypedDict): foo: str bar: typing.List[float] an_int: int - foo #! ['foo: str'] foo #? str() @@ -525,8 +524,10 @@ def typed_dict_test_foo(arg: Foo): #? str() a_string - #? [float()] - a_list_of_strings + #? list() + a_list_of_floats + #? float() + a_list_of_floats[0] #? int() an_int @@ -558,7 +559,41 @@ Foo.set #? ['setdefault'] d.setdefaul -#? 5 ["'foo'"] +#? 5 ["'foo"] d['fo'] #? 5 ['"bar"'] d["bar"] + +class Bar(Foo): + another_variable: int + + #? int() + another_variable + #? str() + foo + #? int() + an_int + +def typed_dict_test_foo(arg: Bar): + a_string = arg['foo'] + a_list_of_floats = arg['bar'] + an_int = arg['an_int'] + another_variable = arg['another_variable'] + + #? str() + a_string + #? list() + a_list_of_floats + #? float() + a_list_of_floats[0] + #? int() + an_int + #? int() + another_variable + + #? ['isupper'] + a_string.isuppe + #? ['pop'] + a_list_of_floats.po + #? ['as_integer_ratio'] + an_int.as_integer_rati From c7d1b8de9e9dbb0f2413c51982d9220ca4eee818 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Wed, 12 Feb 2020 22:45:00 +0000 Subject: [PATCH 15/30] Tell sith that 'completions' became 'complete' --- sith.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sith.py b/sith.py index 34823f59..2c2ed5f7 100755 --- a/sith.py +++ b/sith.py @@ -157,7 +157,7 @@ class TestCase(object): def show_operation(self): print("%s:\n" % self.operation.capitalize()) - if self.operation == 'completions': + if self.operation == 'complete': self.show_completions() else: self.show_definitions() From d6f6c29a636f631214719494005ed0204846b518 Mon Sep 17 00:00:00 2001 From: Sam Roeca Date: Thu, 13 Feb 2020 10:43:41 -0500 Subject: [PATCH 16/30] TypedDict test: fix Bar inheritance checks Note: foo is defined as a function a the module level so I remove it from consideration here to avoid complicating this test with other tests in the module. --- test/completion/pep0484_typing.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index 6472880d..73ab32ee 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -569,9 +569,7 @@ class Bar(Foo): #? int() another_variable - #? str() - foo - #? int() + #? an_int def typed_dict_test_foo(arg: Bar): From c4cf0d78e1d9854c4ff0ef912b2b841c6da0bd37 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sat, 15 Feb 2020 10:51:24 +0000 Subject: [PATCH 17/30] Add a couple of docstrings These are based on observation of the outputs of these functions. --- jedi/inference/param.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/jedi/inference/param.py b/jedi/inference/param.py index 27bd60a4..c1ce541a 100644 --- a/jedi/inference/param.py +++ b/jedi/inference/param.py @@ -51,6 +51,25 @@ class ExecutedParamName(ParamName): def get_executed_param_names_and_issues(function_value, arguments): + """ + Return a tuple of: + - a list of `ExecutedParamName`s corresponding to the arguments of the + function execution `function_value`, containing the inferred value of + those arguments (whether explicit or default) + - a list of the issues encountered while building that list + + For example, given: + ``` + def foo(a, b, c=None, d='d'): ... + + foo(42, c='c') + ``` + + Then for the execution of `foo`, this will return a tuple containing: + - a list with entries for each parameter a, b, c & d; the entries for a, + c, & d will have their values (42, 'c' and 'd' respectively) included. + - a list with a single entry about the lack of a value for `b` + """ def too_many_args(argument): m = _error_argument_count(funcdef, len(unpacked_va)) # Just report an error for the first param that is not needed (like @@ -207,6 +226,23 @@ def get_executed_param_names_and_issues(function_value, arguments): def get_executed_param_names(function_value, arguments): + """ + Return a list of `ExecutedParamName`s corresponding to the arguments of the + function execution `function_value`, containing the inferred value of those + arguments (whether explicit or default). Any issues building this list (for + example required arguments which are missing in the invocation) are ignored. + + For example, given: + ``` + def foo(a, b, c=None, d='d'): ... + + foo(42, c='c') + ``` + + Then for the execution of `foo`, this will return a list containing entries + for each parameter a, b, c & d; the entries for a, c, & d will have their + values (42, 'c' and 'd' respectively) included. + """ return get_executed_param_names_and_issues(function_value, arguments)[0] From 1874e9be8132fec2ece54b12548ec551f0020abc Mon Sep 17 00:00:00 2001 From: Lior Goldberg Date: Thu, 20 Feb 2020 00:44:35 +0200 Subject: [PATCH 18/30] Remove the word 'class' from annotation_string Currently, 'foo(x: int)' results with annotation_string="". Change this to 'int'. --- AUTHORS.txt | 1 + jedi/inference/compiled/access.py | 7 ++++++- test/test_api/test_interpreter.py | 6 +++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index f30d0c01..0cd22f7e 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -55,5 +55,6 @@ Max Woerner Chase (@mwchase) Johannes Maria Frank (@jmfrank63) Shane Steinert-Threlkeld (@shanest) Tim Gates (@timgates42) +Lior Goldberg (@goldberglior) Note: (@user) means a github user name. diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index f76a645f..53f22c70 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -484,6 +484,11 @@ class DirectObjectAccess(object): def needs_type_completions(self): return inspect.isclass(self._obj) and self._obj != type + def _annotation_to_str(self, annotation): + if isinstance(annotation, type): + return str(annotation.__name__) + return str(annotation) + def get_signature_params(self): return [ SignatureParam( @@ -493,7 +498,7 @@ class DirectObjectAccess(object): default_string=repr(p.default), has_annotation=p.annotation is not p.empty, annotation=self._create_access_path(p.annotation), - annotation_string=str(p.annotation), + annotation_string=self._annotation_to_str(p.annotation), kind_name=str(p.kind) ) for p in self._get_signature().parameters.values() ] diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 1ec0b741..e32bc72a 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -342,7 +342,7 @@ def test_completion_params(): @pytest.mark.skipif('py_version < 33', reason='inspect.signature was created in 3.3.') def test_completion_param_annotations(): - # Need to define this function not directly in Python. Otherwise Jedi is to + # Need to define this function not directly in Python. Otherwise Jedi is too # clever and uses the Python code instead of the signature object. code = 'def foo(a: 1, b: str, c: int = 1.0) -> bytes: pass' exec_(code, locals()) @@ -354,6 +354,10 @@ def test_completion_param_annotations(): assert [d.name for d in b.infer()] == ['str'] assert {d.name for d in c.infer()} == {'int', 'float'} + assert a.description == 'param a: 1' + assert b.description == 'param b: str' + assert c.description == 'param c: int=1.0' + d, = jedi.Interpreter('foo()', [locals()]).infer() assert d.name == 'bytes' From 30738a092b30c1f00fbdf4cbb53207c93a7098cf Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 23 Feb 2020 12:29:04 +0000 Subject: [PATCH 19/30] Update sith's module docstring to match the available operations --- sith.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sith.py b/sith.py index 2c2ed5f7..746bac4a 100755 --- a/sith.py +++ b/sith.py @@ -20,8 +20,7 @@ Run a specific operation ./sith.py run -Where operation is one of completions, goto_assignments, goto_definitions, -usages, or call_signatures. +Where operation is one of complete, goto, infer, get_references or get_signatures. Note: Line numbers start at 1; columns start at 0 (this is consistent with many text editors, including Emacs). @@ -95,6 +94,7 @@ class TestCase(object): args = json.load(f) return cls(*args) + # Changing this? Also update the module docstring above. operations = ['complete', 'goto', 'infer', 'get_references', 'get_signatures'] @classmethod From fa63c92cf7f1a067599870517c3e1bb74e41a7e1 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 1 Mar 2020 01:56:49 +0100 Subject: [PATCH 20/30] Simplify tests a bit --- test/completion/pep0484_typing.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index 73ab32ee..382f75d0 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -573,25 +573,13 @@ class Bar(Foo): an_int def typed_dict_test_foo(arg: Bar): - a_string = arg['foo'] - a_list_of_floats = arg['bar'] - an_int = arg['an_int'] - another_variable = arg['another_variable'] - #? str() - a_string + arg['foo'] #? list() - a_list_of_floats + arg['bar'] #? float() - a_list_of_floats[0] + arg['bar'][0] #? int() - an_int + arg['an_int'] #? int() - another_variable - - #? ['isupper'] - a_string.isuppe - #? ['pop'] - a_list_of_floats.po - #? ['as_integer_ratio'] - an_int.as_integer_rati + arg['another_variable'] From 609737322d55090243ffecf0c26c7225859bf83d Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 1 Mar 2020 02:34:38 +0100 Subject: [PATCH 21/30] TypedDict checking should be at a later point --- jedi/inference/gradual/typing.py | 27 --------------------------- jedi/inference/syntax_tree.py | 3 --- jedi/inference/value/klass.py | 4 ++++ test/completion/pep0484_typing.py | 2 ++ 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index be353391..e45e4f98 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -350,33 +350,6 @@ class TypedDictBase(BaseTypingValue): """ -class TypedDictClass(Value): - """ - This represents a class defined like: - - class Foo(TypedDict): - bar: str - """ - def __init__(self, definition_class): - super().__init__(definition_class.inference_state, definition_class.parent_context) - self.tree_node = definition_class.tree_node - self._definition_class = definition_class - - def get_filters(self, origin_scope=None): - """ - A TypedDict doesn't have attributes. - """ - o, = self.inference_state.builtins_module.py__getattribute__('object') - return o.get_filters() - - @property - def name(self): - return ValueName(self, self.tree_node.name) - - def py__call__(self, arguments): - return ValueSet({TypedDict(self._definition_class)}) - - class TypedDict(LazyValueWrapper): """Represents the instance version of ``TypedDictClass``.""" def __init__(self, definition_class): diff --git a/jedi/inference/syntax_tree.py b/jedi/inference/syntax_tree.py index bc902020..6411fc81 100644 --- a/jedi/inference/syntax_tree.py +++ b/jedi/inference/syntax_tree.py @@ -26,7 +26,6 @@ from jedi.inference.compiled.access import COMPARISON_OPERATORS from jedi.inference.cache import inference_state_method_cache from jedi.inference.gradual.stub_value import VersionInfo from jedi.inference.gradual import annotation -from jedi.inference.gradual.typing import TypedDictClass from jedi.inference.names import TreeNameDefinition from jedi.inference.context import CompForContext from jedi.inference.value.decorator import Decoratee @@ -749,8 +748,6 @@ def _apply_decorators(context, node): parent_context=context, tree_node=node ) - if decoratee_value.is_typeddict(): - decoratee_value = TypedDictClass(decoratee_value) else: decoratee_value = FunctionValue.from_context(context, node) initial = values = ValueSet([decoratee_value]) diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index 41c669d7..afefb481 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -133,6 +133,10 @@ class ClassMixin(object): def py__call__(self, arguments=None): from jedi.inference.value import TreeInstance + + from jedi.inference.gradual.typing import TypedDict + if self.is_typeddict(): + return ValueSet([TypedDict(self)]) return ValueSet([TreeInstance(self.inference_state, self.parent_context, self, arguments)]) def py__class__(self): diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index 382f75d0..ad3fc51f 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -558,6 +558,8 @@ d.foo Foo.set #? ['setdefault'] d.setdefaul +#? [] +Foo.setdefaul #? 5 ["'foo"] d['fo'] From 4d5373d626455c2ba7fd17ba39e8b9354e786d6d Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Thu, 27 Feb 2020 18:47:13 +0100 Subject: [PATCH 22/30] Don't continue searching for values if an annotation is found --- jedi/inference/syntax_tree.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jedi/inference/syntax_tree.py b/jedi/inference/syntax_tree.py index 6411fc81..e1442e10 100644 --- a/jedi/inference/syntax_tree.py +++ b/jedi/inference/syntax_tree.py @@ -388,6 +388,7 @@ def _infer_expr_stmt(context, stmt, seek_name=None): debug.dbg('infer_expr_stmt %s (%s)', stmt, seek_name) rhs = stmt.get_rhs() + value_set = context.infer_node(rhs) if seek_name: @@ -655,16 +656,18 @@ def tree_name_to_values(inference_state, context, tree_name): # First check for annotations, like: `foo: int = 3` if module_node is not None: names = module_node.get_used_names().get(tree_name.value, []) + found_annotation = False for name in names: expr_stmt = name.parent if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign": correct_scope = parser_utils.get_parent_scope(name) == context.tree_node if correct_scope: + found_annotation = True value_set |= annotation.infer_annotation( context, expr_stmt.children[1].children[1] ).execute_annotation() - if value_set: + if found_annotation: return value_set types = [] From afc61c25761072729fb223a5cc970262579d2f7e Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 1 Mar 2020 12:26:40 +0100 Subject: [PATCH 23/30] is_typeddict should be part of ClassMixin --- jedi/inference/value/klass.py | 60 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index afefb481..9f7e4982 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -230,6 +230,36 @@ class ClassMixin(object): return 'Type[%s]' % self.py__name__() return self.py__name__() + @inference_state_method_cache(default=False) + def is_typeddict(self): + # TODO Do a proper mro resolution. Currently we are just listing + # classes. However, it's a complicated algorithm. + from jedi.inference.gradual.typing import TypedDictBase + for lazy_cls in self.py__bases__(): + if not isinstance(lazy_cls, LazyTreeValue): + return False + tree_node = lazy_cls.data + # Only resolve simple classes, stuff like Iterable[str] are more + # intensive to resolve and if generics are involved, we know it's + # not a TypedDict. + if not expr_is_dotted(tree_node): + return False + + for cls in lazy_cls.infer(): + if isinstance(cls, TypedDictBase): + return True + try: + method = cls.is_typeddict + except AttributeError: + # We're only dealing with simple classes, so just returning + # here should be fine. This only happens with e.g. compiled + # classes. + return False + else: + if method(): + return True + return False + class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)): api_type = u'class' @@ -274,36 +304,6 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase self.inference_state.builtins_module.py__getattribute__('object') )] - @inference_state_method_cache(default=False) - def is_typeddict(self): - # TODO Do a proper mro resolution. Currently we are just listing - # classes. However, it's a complicated algorithm. - from jedi.inference.gradual.typing import TypedDictBase - for lazy_cls in self.py__bases__(): - if not isinstance(lazy_cls, LazyTreeValue): - return False - tree_node = lazy_cls.data - # Only resolve simple classes, stuff like Iterable[str] are more - # intensive to resolve and if generics are involved, we know it's - # not a TypedDict. - if not expr_is_dotted(tree_node): - return False - - for cls in lazy_cls.infer(): - if isinstance(cls, TypedDictBase): - return True - try: - method = cls.is_typeddict - except AttributeError: - # We're only dealing with simple classes, so just returning - # here should be fine. This only happens with e.g. compiled - # classes. - return False - else: - if method(): - return True - return False - def py__getitem__(self, index_value_set, contextualized_node): from jedi.inference.gradual.base import GenericClass if not index_value_set: From bd1ef659e8d1094b89547fdf3afd8faee3e3e2c1 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 1 Mar 2020 12:47:26 +0100 Subject: [PATCH 24/30] Make InterpreterEnvironment public --- jedi/__init__.py | 2 +- jedi/inference/compiled/subprocess/__init__.py | 2 +- test/conftest.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jedi/__init__.py b/jedi/__init__.py index 5ddb0d04..f540b64e 100644 --- a/jedi/__init__.py +++ b/jedi/__init__.py @@ -40,7 +40,7 @@ from jedi.api import Script, Interpreter, set_debug_function, \ from jedi import settings from jedi.api.environment import find_virtualenvs, find_system_environments, \ get_default_environment, InvalidPythonEnvironment, create_environment, \ - get_system_environment + get_system_environment, InterpreterEnvironment from jedi.api.exceptions import InternalError # Finally load the internal plugins. This is only internal. from jedi.plugins import registry diff --git a/jedi/inference/compiled/subprocess/__init__.py b/jedi/inference/compiled/subprocess/__init__.py index bad1803c..246cffa5 100644 --- a/jedi/inference/compiled/subprocess/__init__.py +++ b/jedi/inference/compiled/subprocess/__init__.py @@ -297,7 +297,7 @@ class Listener(object): try: inference_state = self._inference_states[inference_state_id] except KeyError: - from jedi.api.environment import InterpreterEnvironment + from jedi import InterpreterEnvironment inference_state = InferenceState( # The project is not actually needed. Nothing should need to # access it. diff --git a/test/conftest.py b/test/conftest.py index 8fe64c38..750cbc1b 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -8,7 +8,7 @@ import pytest from . import helpers from . import run from . import refactor -from jedi.api.environment import InterpreterEnvironment, get_system_environment +from jedi import InterpreterEnvironment, get_system_environment from jedi.inference.compiled.value import create_from_access_path from jedi.inference.imports import _load_python_module from jedi.file_io import KnownContentFileIO From 656324f686a9d4d4f972f2d41dfcb52296da4d7c Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 1 Mar 2020 13:30:41 +0100 Subject: [PATCH 25/30] Disable some more tests for Python 2 --- jedi/inference/gradual/typing.py | 4 +++- test/test_integration.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index e45e4f98..85a34396 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -6,6 +6,8 @@ values. This file deals with all the typing.py cases. """ import itertools + +from jedi._compatibility import unicode from jedi import debug from jedi.inference.compiled import builtin_from_name, create_simple_object from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \ @@ -363,7 +365,7 @@ class TypedDict(LazyValueWrapper): return ValueName(self, self.tree_node.name) def py__simple_getitem__(self, index): - if isinstance(index, str): + if isinstance(index, unicode): return ValueSet.from_sets( name.infer() for filter in self._definition_class.get_filters(is_instance=True) diff --git a/test/test_integration.py b/test/test_integration.py index 64239048..be447773 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -1,4 +1,5 @@ import os +import sys import pytest @@ -36,6 +37,9 @@ def test_completion(case, monkeypatch, environment, has_typing): if skip_reason is not None: pytest.skip(skip_reason) + if 'pep0484_typing' in case.path and sys.version_info[0] == 2: + pytest.skip('ditch python 2 finally') + _CONTAINS_TYPING = ('pep0484_typing', 'pep0484_comments', 'pep0526_variables') if not has_typing and any(x in case.path for x in _CONTAINS_TYPING): pytest.skip('Needs the typing module installed to run this test.') From 8df917f1df9ff001b33b7ae91af2c4fc6c549011 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 6 Mar 2020 10:07:23 +0100 Subject: [PATCH 26/30] Fix a getattr_static issue, fixes #1517 --- jedi/inference/compiled/getattr_static.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/jedi/inference/compiled/getattr_static.py b/jedi/inference/compiled/getattr_static.py index b86a46f4..7225aabd 100644 --- a/jedi/inference/compiled/getattr_static.py +++ b/jedi/inference/compiled/getattr_static.py @@ -6,6 +6,7 @@ information returned to enable Jedi to make decisions. import types +from jedi import debug from jedi._compatibility import py_version _sentinel = object() @@ -54,7 +55,14 @@ def _shadowed_dict_newstyle(klass): def _static_getmro_newstyle(klass): - return type.__dict__['__mro__'].__get__(klass) + mro = type.__dict__['__mro__'].__get__(klass) + if not isinstance(mro, (tuple, list)): + # There are unfortunately no tests for this, I was not able to + # reproduce this in pure Python. However should still solve the issue + # raised in GH #1517. + debug.warning('mro of %s returned %s, should be a tuple' % (klass, mro)) + return () + return mro if py_version >= 30: From d574162da32052ca378cf08b3429eef2fd11100d Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Tue, 10 Mar 2020 20:07:10 +0100 Subject: [PATCH 27/30] Fix namedtuple docstring/signature issues, fixes #1506 --- jedi/inference/gradual/typeshed.py | 3 +++ jedi/plugins/stdlib.py | 2 -- test/test_inference/test_stdlib.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/jedi/inference/gradual/typeshed.py b/jedi/inference/gradual/typeshed.py index ab8102e7..d8604fed 100644 --- a/jedi/inference/gradual/typeshed.py +++ b/jedi/inference/gradual/typeshed.py @@ -129,6 +129,9 @@ def import_module_decorator(func): def _try_to_load_stub_cached(inference_state, import_names, *args, **kwargs): + if import_names is None: + return None + try: return inference_state.stub_module_cache[import_names] except KeyError: diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 448f2b50..f3ebd35d 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -44,8 +44,6 @@ from operator import itemgetter as _itemgetter from collections import OrderedDict class {typename}(tuple): - '{typename}({arg_list})' - __slots__ = () _fields = {field_names!r} diff --git a/test/test_inference/test_stdlib.py b/test/test_inference/test_stdlib.py index e17a88b3..0e910d7f 100644 --- a/test/test_inference/test_stdlib.py +++ b/test/test_inference/test_stdlib.py @@ -77,6 +77,7 @@ def test_namedtuple_infer(Script): assert d1.get_line_code() == "class Foo(tuple):\n" assert d1.module_path is None + assert d1.docstring() == 'Foo(id, timestamp, gps_timestamp, attributes)' def test_re_sub(Script, environment): From fb72e1b448da17728553f06e5626beb5a77f361f Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 13 Mar 2020 00:50:25 +0100 Subject: [PATCH 28/30] Merge _remove_statements and infer_expr_stmt, fixes #1504 --- jedi/inference/syntax_tree.py | 25 +++++++------------------ test/completion/recursion.py | 12 ++++++++++++ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/jedi/inference/syntax_tree.py b/jedi/inference/syntax_tree.py index e1442e10..8a0b2656 100644 --- a/jedi/inference/syntax_tree.py +++ b/jedi/inference/syntax_tree.py @@ -356,6 +356,12 @@ def infer_atom(context, atom): def infer_expr_stmt(context, stmt, seek_name=None): with recursion.execution_allowed(context.inference_state, stmt) as allowed: if allowed: + if seek_name is not None: + pep0484_values = \ + annotation.find_type_from_comment_hint_assign(context, stmt, seek_name) + if pep0484_values: + return pep0484_values + return _infer_expr_stmt(context, stmt, seek_name) return NO_VALUES @@ -632,23 +638,6 @@ def _infer_comparison_part(inference_state, context, left, operator, right): return result -def _remove_statements(context, stmt, name): - """ - This is the part where statements are being stripped. - - Due to lazy type inference, statements like a = func; b = a; b() have to be - inferred. - - TODO merge with infer_expr_stmt? - """ - pep0484_values = \ - annotation.find_type_from_comment_hint_assign(context, stmt, name) - if pep0484_values: - return pep0484_values - - return infer_expr_stmt(context, stmt, seek_name=name) - - @plugin_manager.decorate() def tree_name_to_values(inference_state, context, tree_name): value_set = NO_VALUES @@ -713,7 +702,7 @@ def tree_name_to_values(inference_state, context, tree_name): n = TreeNameDefinition(context, tree_name) types = check_tuple_assignments(n, for_types) elif typ == 'expr_stmt': - types = _remove_statements(context, node, tree_name) + types = infer_expr_stmt(context, node, tree_name) elif typ == 'with_stmt': value_managers = context.infer_node(node.get_test_node_from_name(tree_name)) enter_methods = value_managers.py__getattribute__(u'__enter__') diff --git a/test/completion/recursion.py b/test/completion/recursion.py index ebbd69e3..1e882bfa 100644 --- a/test/completion/recursion.py +++ b/test/completion/recursion.py @@ -91,3 +91,15 @@ class B: for i in self.a(i): #? yield i + + +foo = int +foo = foo # type: foo +#? int +foo + +while True: + bar = int + bar = bar # type: bar + #? int() + bar From b4fa42a282b4b2d5dc04d23a1ccdfe4c889567eb Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 13 Mar 2020 02:22:05 +0100 Subject: [PATCH 29/30] Avoid duplicate definitions for goto, fixes #1514 --- jedi/api/__init__.py | 3 ++- test/completion/basic.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 426f56ec..5f75410b 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -318,7 +318,8 @@ class Script(object): ) defs = [classes.Definition(self._inference_state, d) for d in set(names)] - return helpers.sorted_definitions(defs) + # Avoid duplicates + return list(set(helpers.sorted_definitions(defs))) @validate_line_column def help(self, line=None, column=None): diff --git a/test/completion/basic.py b/test/completion/basic.py index 3ff919ca..b0e71bad 100644 --- a/test/completion/basic.py +++ b/test/completion/basic.py @@ -422,3 +422,11 @@ with Foo() as f3: #? 6 Foo with Foo() as f3: f3 + +# ----------------- +# Avoiding multiple definitions +# ----------------- + +some_array = ['', ''] +#! ['def upper'] +some_array[some_not_defined_index].upper From 521e240c5fae81af8bf2843d8f6aea546bc0f52a Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 13 Mar 2020 12:53:05 +0100 Subject: [PATCH 30/30] Changed semantics of ClassVar attributes in classes, fixes #1502 --- jedi/inference/value/klass.py | 6 +++++- test/completion/pep0526_variables.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index 9f7e4982..13a483e3 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -115,7 +115,11 @@ class ClassFilter(ParserTreeFilter): annassign = expr_stmt.children[1] if annassign.type == 'annassign': # TODO this is not proper matching - if 'ClassVar' not in annassign.children[1].get_code(): + + # If there is an =, the variable is obviously also + # defined on the class. + if 'ClassVar' not in annassign.children[1].get_code() \ + and '=' not in annassign.children: return False # Filter for name mangling of private variables like __foo diff --git a/test/completion/pep0526_variables.py b/test/completion/pep0526_variables.py index 6af77d91..39168339 100644 --- a/test/completion/pep0526_variables.py +++ b/test/completion/pep0526_variables.py @@ -57,7 +57,7 @@ Foo.baz Foo().baz class VarClass: - var_instance1: int = 1 + var_instance1: int = '' var_instance2: float var_class1: typing.ClassVar[str] = 1 var_class2: typing.ClassVar[bytes] @@ -77,9 +77,9 @@ class VarClass: self.var_ -#? ['var_class1', 'var_class2'] +#? ['var_class1', 'var_class2', 'var_instance1'] VarClass.var_ -#? +#? int() VarClass.var_instance1 #? VarClass.var_instance2