From 4783c065dafb81353b1aa667c88b6a6e8f410601 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 21:26:46 +0100 Subject: [PATCH 01/74] Configure editors for uniform whitespace handling --- .editorconfig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..5374960a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 + +[*.md] +indent_size = 2 From 9505dabfef7483306ae62232f8a1fa4a0aca687b Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 21:28:56 +0100 Subject: [PATCH 02/74] Reflow for linting --- jedi/inference/gradual/annotation.py | 6 ++++-- jedi/settings.py | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/jedi/inference/gradual/annotation.py b/jedi/inference/gradual/annotation.py index fa3aa5ae..eadcfc2e 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -53,8 +53,10 @@ def _infer_annotation_string(context, string, index=None): value_set = context.infer_node(node) if index is not None: value_set = value_set.filter( - lambda value: value.array_type == 'tuple' # noqa - and len(list(value.py__iter__())) >= index + lambda value: ( + value.array_type == 'tuple' + and len(list(value.py__iter__())) >= index + ) ).py__simple_getitem__(index) return value_set diff --git a/jedi/settings.py b/jedi/settings.py index 6764a3e5..ced021e1 100644 --- a/jedi/settings.py +++ b/jedi/settings.py @@ -69,8 +69,11 @@ Adds an opening bracket after a function for completions. # ---------------- if platform.system().lower() == 'windows': - _cache_directory = os.path.join(os.getenv('LOCALAPPDATA') or - os.path.expanduser('~'), 'Jedi', 'Jedi') + _cache_directory = os.path.join( + os.getenv('LOCALAPPDATA') or os.path.expanduser('~'), + 'Jedi', + 'Jedi', + ) elif platform.system().lower() == 'darwin': _cache_directory = os.path.join('~', 'Library', 'Caches', 'Jedi') else: From 6ef18bea502eba818301f528cfb5a178b74b6c09 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 21:34:37 +0100 Subject: [PATCH 03/74] Make this noqa more specific --- jedi/inference/base_value.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index c0f960d4..c5b2cec0 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -111,7 +111,7 @@ class HelperValueMixin(object): .py__getattribute__('__anext__').execute_with_values() .py__getattribute__('__await__').execute_with_values() .py__stop_iteration_returns() - ) # noqa + ) # noqa: E124 ]) return self.py__iter__(contextualized_node) From 5e6138d16f867063c46d3e0302d953b6be1df60b Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 21:34:58 +0100 Subject: [PATCH 04/74] Update to flake8 3.8.x In particular this improves support for detecting usage of various type annotation usages and adds support for correctly parsing type: ignore comments which contain a reason tag. --- setup.cfg | 2 ++ setup.py | 2 +- test/test_api/test_call_signatures.py | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 39633dc1..6929ce30 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,6 +13,8 @@ ignore = E721, # Line break before binary operator W503, + # Single letter loop variables are often fine + E741, exclude = jedi/third_party/* .tox/* [pycodestyle] diff --git a/setup.py b/setup.py index 044d7d5a..bf9be8c2 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ setup(name='jedi', 'Django<3.1', # For now pin this. ], 'qa': [ - 'flake8==3.7.9', + 'flake8==3.8.3', ], }, package_data={'jedi': ['*.pyi', 'third_party/typeshed/LICENSE', diff --git a/test/test_api/test_call_signatures.py b/test/test_api/test_call_signatures.py index 59e3a640..2f0cf90c 100644 --- a/test/test_api/test_call_signatures.py +++ b/test/test_api/test_call_signatures.py @@ -122,10 +122,10 @@ def test_multiple_signatures(Script): def test_get_signatures_whitespace(Script): s = dedent("""\ - abs( + abs( def x(): pass - """) # noqa + """) assert_signature(Script, s, 'abs', 0, line=1, column=5) From 403564315c3111ae26b0240836eb84a42e94d4e6 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 22:44:42 +0100 Subject: [PATCH 05/74] Reflow test to ensure trailing space is preserved Many editors strip trailing space, so avoid using a multiline string where the space is actually needed. --- test/test_api/test_call_signatures.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/test_api/test_call_signatures.py b/test/test_api/test_call_signatures.py index 2f0cf90c..664eec86 100644 --- a/test/test_api/test_call_signatures.py +++ b/test/test_api/test_call_signatures.py @@ -121,11 +121,8 @@ def test_multiple_signatures(Script): def test_get_signatures_whitespace(Script): - s = dedent("""\ - abs( - def x(): - pass - """) + # note: trailing space after 'abs' + s = 'abs( \ndef x():\n pass\n' assert_signature(Script, s, 'abs', 0, line=1, column=5) From b651c6541a6bf881ef09ac761b2cfbc85d3a1eea Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 23:15:20 +0100 Subject: [PATCH 06/74] Configure travis' flake8 call more explicitly I'm basing this on '{posargs:jedi}' looking like it was a tox thing, which we're no longer using. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d7c418a4..89102f4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ env: matrix: include: - python: 3.8 - script: + script: - 'pip install coverage' - 'coverage run --source jedi -m pytest' - 'coverage report' @@ -31,7 +31,7 @@ matrix: - 'pip install .[qa]' script: # Ignore F401, which are unused imports. flake8 is a primitive tool and is sometimes wrong. - - 'flake8 --extend-ignore F401 {posargs:jedi}' + - 'flake8 --extend-ignore F401 jedi setup.py' install: - sudo apt-get -y install python3-venv - pip install .[testing] From e86afc17058c5320245a5e0e035e4bd0e8e3f62f Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Thu, 23 Jul 2020 01:32:37 +0200 Subject: [PATCH 07/74] _cropped_file_size should be an int, fixes #1639 --- jedi/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/settings.py b/jedi/settings.py index 6764a3e5..8e6b7bef 100644 --- a/jedi/settings.py +++ b/jedi/settings.py @@ -98,7 +98,7 @@ parse the parts again that have changed, while reusing the rest of the syntax tree. """ -_cropped_file_size = 10e6 # 1 Megabyte +_cropped_file_size = int(10e6) # 1 Megabyte """ Jedi gets extremely slow if the file size exceed a few thousand lines. To avoid getting stuck completely Jedi crops the file at some point. From 480c352d3340acf8a75164d3dcff441169259d04 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 16:07:45 +0100 Subject: [PATCH 08/74] Python 3.6 embeddable doesn't have pydoc_data This reinstates the import check for pydoc_data for now. Specifically I looked in the following: - python-3.6.8-embed-amd64.zip: missing pydoc_data - python-3.7.8-embed-amd64.zip: present - python-3.8.5-embed-amd64.zip: present --- jedi/api/keywords.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jedi/api/keywords.py b/jedi/api/keywords.py index c016dadf..a0146de0 100644 --- a/jedi/api/keywords.py +++ b/jedi/api/keywords.py @@ -3,7 +3,11 @@ from contextlib import suppress from jedi.inference.names import AbstractArbitraryName -from pydoc_data import topics as pydoc_topics +try: + from pydoc_data import topics as pydoc_topics +except ImportError: + # Python 3.6.8 embeddable does not have pydoc_data. + pydoc_topics = None class KeywordName(AbstractArbitraryName): From c09e21ae4bf63baa5723f73260581a50f317cb46 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 22:19:24 +0100 Subject: [PATCH 09/74] Configure mypy No fixes yet, this just gets the config in place. Note: I'm assuming that we'll pick up a change to parso such that it exposes its type stubs here. Otherwise we'll want to tweak the imports config to ignore those errors. --- .travis.yml | 1 + setup.cfg | 32 ++++++++++++++++++++++++++++++++ setup.py | 1 + 3 files changed, 34 insertions(+) diff --git a/.travis.yml b/.travis.yml index 89102f4a..e3b32895 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ matrix: script: # Ignore F401, which are unused imports. flake8 is a primitive tool and is sometimes wrong. - 'flake8 --extend-ignore F401 jedi setup.py' + - 'mypy jedi' install: - sudo apt-get -y install python3-venv - pip install .[testing] diff --git a/setup.cfg b/setup.cfg index 6929ce30..54fd65db 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,3 +19,35 @@ exclude = jedi/third_party/* .tox/* [pycodestyle] max-line-length = 100 + + +[mypy] +# Ensure generics are explicit about what they are (e.g: `List[str]` rather than +# just `List`) +disallow_any_generics = True + +disallow_subclassing_any = True + +# Ensure that optional types are explicit +no_implicit_optional = True +strict_optional = True + +# Avoid creating future gotchas emerging from bad typing +warn_redundant_casts = True +warn_unused_ignores = True +warn_return_any = True +warn_unused_configs = True + +warn_unreachable = True + +# Require values to be explicitly re-exported; this makes things easier for +# Flake8 too and avoids accidentally importing thing from the "wrong" place +# (which helps avoid circular imports) +implicit_reexport = False + +strict_equality = True + +[mypy-jedi.debug] +# jedi.debug is configured by setting module-level values, which mypy doesn't +# know about. +warn_unreachable = False diff --git a/setup.py b/setup.py index bf9be8c2..b4f9da2a 100755 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ setup(name='jedi', ], 'qa': [ 'flake8==3.8.3', + 'mypy==0.782', ], }, package_data={'jedi': ['*.pyi', 'third_party/typeshed/LICENSE', From 35c2d660cb63a2dcbc4026c1d1d386a409695294 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 23:30:26 +0100 Subject: [PATCH 10/74] Fix most import related mypy errors --- jedi/api/classes.py | 9 ++++----- jedi/debug.py | 4 ++-- jedi/inference/compiled/__init__.py | 16 ++++++++++++++++ jedi/inference/docstrings.py | 2 +- jedi/inference/references.py | 3 ++- jedi/inference/value/__init__.py | 15 +++++++++++++++ jedi/inference/value/instance.py | 2 +- jedi/plugins/pytest.py | 2 +- 8 files changed, 42 insertions(+), 11 deletions(-) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 679d1e59..a770ee52 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -17,16 +17,15 @@ import re import warnings from typing import Optional -from parso.python.tree import search_ancestor +from parso.tree import search_ancestor from jedi import settings from jedi import debug from jedi.inference.utils import unite from jedi.cache import memoize_method -from jedi.inference import imports -from jedi.inference.imports import ImportName from jedi.inference.compiled.mixed import MixedName -from jedi.inference.gradual.typeshed import StubModuleValue +from jedi.inference.names import ImportName, SubModuleName +from jedi.inference.gradual.stub_value import StubModuleValue from jedi.inference.gradual.conversion import convert_names, convert_values from jedi.inference.base_value import ValueSet from jedi.api.keywords import KeywordName @@ -186,7 +185,7 @@ class BaseName(object): tree_name.is_definition(): resolve = True - if isinstance(self._name, imports.SubModuleName) or resolve: + if isinstance(self._name, SubModuleName) or resolve: for value in self._name.infer(): return value.api_type return self._name.api_type diff --git a/jedi/debug.py b/jedi/debug.py index 4066cff7..8c7c883f 100644 --- a/jedi/debug.py +++ b/jedi/debug.py @@ -20,7 +20,7 @@ try: raise ImportError else: # Use colorama for nicer console output. - from colorama import Fore, init + from colorama import Fore, init # type: ignore[import] from colorama import initialise def _lazy_colorama_init(): # noqa: F811 @@ -45,7 +45,7 @@ try: _inited = True except ImportError: - class Fore(object): + class Fore(object): # type: ignore[no-redef] RED = '' GREEN = '' YELLOW = '' diff --git a/jedi/inference/compiled/__init__.py b/jedi/inference/compiled/__init__.py index faf5d373..f4a8691e 100644 --- a/jedi/inference/compiled/__init__.py +++ b/jedi/inference/compiled/__init__.py @@ -2,6 +2,22 @@ from jedi.inference.compiled.value import CompiledValue, CompiledName, \ CompiledValueFilter, CompiledValueName, create_from_access_path from jedi.inference.base_value import LazyValueWrapper +__all__ = ( + 'CompiledValue', + 'CompiledName', + 'CompiledValueFilter', + 'CompiledValueName', + 'create_from_access_path', + + 'LazyValueWrapper', + + 'builtin_from_name', + 'ExactValue', + 'create_simple_object', + 'get_string_value_set', + 'load_module', +) + def builtin_from_name(inference_state, string): typing_builtins_module = inference_state.builtins_module diff --git a/jedi/inference/docstrings.py b/jedi/inference/docstrings.py index e9c94cdc..ee7a8d89 100644 --- a/jedi/inference/docstrings.py +++ b/jedi/inference/docstrings.py @@ -50,7 +50,7 @@ def _get_numpy_doc_string_cls(): global _numpy_doc_string_cache if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)): raise _numpy_doc_string_cache - from numpydoc.docscrape import NumpyDocString + from numpydoc.docscrape import NumpyDocString # type: ignore[import] _numpy_doc_string_cache = NumpyDocString return _numpy_doc_string_cache diff --git a/jedi/inference/references.py b/jedi/inference/references.py index e1b97c41..71d7918a 100644 --- a/jedi/inference/references.py +++ b/jedi/inference/references.py @@ -5,7 +5,8 @@ from parso import python_bytes_to_unicode from jedi.debug import dbg from jedi.file_io import KnownContentFileIO -from jedi.inference.imports import SubModuleName, load_module_from_path +from jedi.inference.names import SubModuleName +from jedi.inference.imports import load_module_from_path from jedi.inference.filters import ParserTreeFilter from jedi.inference.gradual.conversion import convert_names diff --git a/jedi/inference/value/__init__.py b/jedi/inference/value/__init__.py index 2e17ba26..d35f0647 100644 --- a/jedi/inference/value/__init__.py +++ b/jedi/inference/value/__init__.py @@ -4,3 +4,18 @@ from jedi.inference.value.function import FunctionValue, \ MethodValue from jedi.inference.value.instance import AnonymousInstance, BoundMethod, \ CompiledInstance, AbstractInstanceValue, TreeInstance + +__all__ = ( + 'ModuleValue', + + 'ClassValue', + + 'FunctionValue', + 'MethodValue', + + 'AnonymousInstance', + 'BoundMethod', + 'CompiledInstance', + 'AbstractInstanceValue', + 'TreeInstance', +) diff --git a/jedi/inference/value/instance.py b/jedi/inference/value/instance.py index 153ab4f4..9514e01d 100644 --- a/jedi/inference/value/instance.py +++ b/jedi/inference/value/instance.py @@ -1,6 +1,6 @@ from abc import abstractproperty -from parso.python.tree import search_ancestor +from parso.tree import search_ancestor from jedi import debug from jedi import settings diff --git a/jedi/plugins/pytest.py b/jedi/plugins/pytest.py index 432385e3..cec23733 100644 --- a/jedi/plugins/pytest.py +++ b/jedi/plugins/pytest.py @@ -1,6 +1,6 @@ from pathlib import Path -from parso.python.tree import search_ancestor +from parso.tree import search_ancestor from jedi.inference.cache import inference_state_method_cache from jedi.inference.imports import load_module_from_path from jedi.inference.filters import ParserTreeFilter From f98a9f79999efb2859ba6d9f2208e3cd6c6acb71 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Wed, 22 Jul 2020 00:08:50 +0100 Subject: [PATCH 11/74] Annotate the completions cache --- jedi/api/completion_cache.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/jedi/api/completion_cache.py b/jedi/api/completion_cache.py index 6dac6a4e..46e9bead 100644 --- a/jedi/api/completion_cache.py +++ b/jedi/api/completion_cache.py @@ -1,7 +1,13 @@ -_cache = {} +from typing import Dict, Tuple, Callable + +CacheValues = Tuple[str, str, str] +CacheValuesCallback = Callable[[], CacheValues] -def save_entry(module_name, name, cache): +_cache: Dict[str, Dict[str, CacheValues]] = {} + + +def save_entry(module_name: str, name: str, cache: CacheValues) -> None: try: module_cache = _cache[module_name] except KeyError: @@ -9,8 +15,8 @@ def save_entry(module_name, name, cache): module_cache[name] = cache -def _create_get_from_cache(number): - def _get_from_cache(module_name, name, get_cache_values): +def _create_get_from_cache(number: int) -> Callable[[str, str, CacheValuesCallback], str]: + def _get_from_cache(module_name: str, name: str, get_cache_values: CacheValuesCallback) -> str: try: return _cache[module_name][name][number] except KeyError: From 1418aada9114f14eba0bccbc13354c58c684b1ed Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 23:46:48 +0100 Subject: [PATCH 12/74] Annotate top level items mypy needs annotating --- jedi/cache.py | 5 +++-- jedi/inference/filters.py | 3 +++ jedi/inference/flow_analysis.py | 6 ++++-- jedi/inference/gradual/typeshed.py | 3 ++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/jedi/cache.py b/jedi/cache.py index 29ed979f..1ff45201 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -13,14 +13,15 @@ these variables are being cleaned after every API usage. """ import time from functools import wraps +from typing import Any, Dict, Tuple from jedi import settings from parso.cache import parser_cache -_time_caches = {} +_time_caches: Dict[str, Dict[Any, Tuple[float, Any]]] = {} -def clear_time_caches(delete_all=False): +def clear_time_caches(delete_all: bool = False) -> None: """ Jedi caches many things, that should be completed after each completion finishes. diff --git a/jedi/inference/filters.py b/jedi/inference/filters.py index 6551cebc..35692169 100644 --- a/jedi/inference/filters.py +++ b/jedi/inference/filters.py @@ -3,9 +3,11 @@ Filters are objects that you can use to filter names in different scopes. They are needed for name resolution. """ from abc import abstractmethod +from typing import List, MutableMapping import weakref from parso.tree import search_ancestor +from parso.python.tree import Name, UsedNamesMapping from jedi.inference import flow_analysis from jedi.inference.base_value import ValueSet, ValueWrapper, \ @@ -15,6 +17,7 @@ from jedi.inference.utils import to_list from jedi.inference.names import TreeNameDefinition, ParamName, \ AnonymousParamName, AbstractNameDefinition +_definition_name_cache: MutableMapping[UsedNamesMapping, List[Name]] _definition_name_cache = weakref.WeakKeyDictionary() diff --git a/jedi/inference/flow_analysis.py b/jedi/inference/flow_analysis.py index 184f367f..be67f23c 100644 --- a/jedi/inference/flow_analysis.py +++ b/jedi/inference/flow_analysis.py @@ -1,12 +1,14 @@ +from typing import Dict, Optional + from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope from jedi.inference.recursion import execution_allowed from jedi.inference.helpers import is_big_annoying_library class Status(object): - lookup_table = {} + lookup_table: Dict[Optional[bool], 'Status'] = {} - def __init__(self, value, name): + def __init__(self, value: Optional[bool], name: str) -> None: self._value = value self._name = name Status.lookup_table[value] = self diff --git a/jedi/inference/gradual/typeshed.py b/jedi/inference/gradual/typeshed.py index e186bfc3..6295294d 100644 --- a/jedi/inference/gradual/typeshed.py +++ b/jedi/inference/gradual/typeshed.py @@ -2,6 +2,7 @@ import os import re from functools import wraps from collections import namedtuple +from typing import Dict, Mapping, Tuple from pathlib import Path from jedi import settings @@ -74,7 +75,7 @@ def _get_typeshed_directories(version_info): yield PathInfo(str(base_path.joinpath(check_version)), is_third_party) -_version_cache = {} +_version_cache: Dict[Tuple[int, int], Mapping[str, PathInfo]] = {} def _cache_stub_file_map(version_info): From 9b3cd15c5fe23101a78063a9887fd83fb5d41d76 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 15:01:40 +0100 Subject: [PATCH 13/74] Fix type clash --- jedi/inference/sys_path.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/inference/sys_path.py b/jedi/inference/sys_path.py index b7457e88..5eff58de 100644 --- a/jedi/inference/sys_path.py +++ b/jedi/inference/sys_path.py @@ -14,8 +14,8 @@ from jedi import debug _BUILDOUT_PATH_INSERTION_LIMIT = 10 -def _abs_path(module_context, path: str): - path = Path(path) +def _abs_path(module_context, str_path: str): + path = Path(str_path) if path.is_absolute(): return path From 7d9205d4ae0fdac2b56b29bc0b7e78f5f2a1e832 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 15:04:37 +0100 Subject: [PATCH 14/74] This is actually optional --- jedi/inference/value/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/inference/value/module.py b/jedi/inference/value/module.py index 4de1e621..7ab4c298 100644 --- a/jedi/inference/value/module.py +++ b/jedi/inference/value/module.py @@ -165,7 +165,7 @@ class ModuleValue(ModuleMixin, TreeValue): return None return '.'.join(self.string_names) - def py__file__(self) -> Path: + def py__file__(self) -> Optional[Path]: """ In contrast to Python's __file__ can be None. """ From b1f95b4bf953a9d5fce53d1819f127dba2126002 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 15:08:44 +0100 Subject: [PATCH 15/74] Annotate these attributes --- jedi/inference/filters.py | 6 +++--- jedi/inference/names.py | 5 +++-- jedi/inference/value/module.py | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/jedi/inference/filters.py b/jedi/inference/filters.py index 35692169..05a9efcf 100644 --- a/jedi/inference/filters.py +++ b/jedi/inference/filters.py @@ -3,7 +3,7 @@ Filters are objects that you can use to filter names in different scopes. They are needed for name resolution. """ from abc import abstractmethod -from typing import List, MutableMapping +from typing import List, Optional, MutableMapping, Type import weakref from parso.tree import search_ancestor @@ -15,7 +15,7 @@ from jedi.inference.base_value import ValueSet, ValueWrapper, \ from jedi.parser_utils import get_cached_parent_scope from jedi.inference.utils import to_list from jedi.inference.names import TreeNameDefinition, ParamName, \ - AnonymousParamName, AbstractNameDefinition + AnonymousParamName, AbstractNameDefinition, NameWrapper _definition_name_cache: MutableMapping[UsedNamesMapping, List[Name]] _definition_name_cache = weakref.WeakKeyDictionary() @@ -39,7 +39,7 @@ class AbstractFilter(object): class FilterWrapper(object): - name_wrapper_class = None + name_wrapper_class: Optional[Type[NameWrapper]] = None def __init__(self, wrapped_filter): self._wrapped_filter = wrapped_filter diff --git a/jedi/inference/names.py b/jedi/inference/names.py index 81161359..1d20ba5f 100644 --- a/jedi/inference/names.py +++ b/jedi/inference/names.py @@ -1,5 +1,6 @@ from abc import abstractmethod from inspect import Parameter +from typing import Optional, Tuple from parso.tree import search_ancestor @@ -24,8 +25,8 @@ def _merge_name_docs(names): class AbstractNameDefinition(object): - start_pos = None - string_name = None + start_pos: Optional[Tuple[int, int]] = None + string_name: Optional[str] = None parent_context = None tree_name = None is_value_name = True diff --git a/jedi/inference/value/module.py b/jedi/inference/value/module.py index 7ab4c298..b996d814 100644 --- a/jedi/inference/value/module.py +++ b/jedi/inference/value/module.py @@ -1,5 +1,6 @@ import os from pathlib import Path +from typing import Optional from jedi.inference.cache import inference_state_method_cache from jedi.inference.names import AbstractNameDefinition, ModuleName @@ -145,7 +146,7 @@ class ModuleValue(ModuleMixin, TreeValue): ) self.file_io = file_io if file_io is None: - self._path = None + self._path: Optional[Path] = None else: self._path = Path(file_io.path) self.string_names = string_names # Optional[Tuple[str, ...]] From 1c87ae378de871b6b0e7d2f904806cf36adf7705 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 15:45:44 +0100 Subject: [PATCH 16/74] This is a Path now --- jedi/api/classes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index a770ee52..774a9f98 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -15,6 +15,7 @@ the interesting information about all operations. """ import re import warnings +from pathlib import Path from typing import Optional from parso.tree import search_ancestor @@ -92,11 +93,11 @@ class BaseName(object): return self._name.get_root_context() @property - def module_path(self) -> Optional[str]: + def module_path(self) -> Optional[Path]: """ Shows the file path of a module. e.g. ``/usr/lib/python3.9/os.py`` - :rtype: str or None + :rtype: Path or None """ module = self._get_module_context() if module.is_stub() or not module.is_compiled(): From 07fbcd2262ead74f16a26c797a645a36ae26aaa9 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 15:49:08 +0100 Subject: [PATCH 17/74] Make this explicitly expect a Path --- jedi/api/classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 774a9f98..f4372df3 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -103,7 +103,7 @@ class BaseName(object): if module.is_stub() or not module.is_compiled(): # Compiled modules should not return a module path even if they # have one. - path = self._get_module_context().py__file__() + path: Optional[Path] = self._get_module_context().py__file__() if path is not None: return path From 5e509814f70087c0494ba4b958995d0bc3fbe4dd Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 15:50:24 +0100 Subject: [PATCH 18/74] Ignore mypy not coping with decorated properties --- jedi/api/classes.py | 2 +- jedi/inference/__init__.py | 4 ++-- jedi/inference/value/module.py | 2 +- jedi/inference/value/namespace.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index f4372df3..5c746cd9 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -497,7 +497,7 @@ class BaseName(object): return [self if n == self._name else Name(self._inference_state, n) for n in resulting_names] - @property + @property # type: ignore[misc] @memoize_method def params(self): warnings.warn( diff --git a/jedi/inference/__init__.py b/jedi/inference/__init__.py index 68217365..fc2bf0c3 100644 --- a/jedi/inference/__init__.py +++ b/jedi/inference/__init__.py @@ -120,14 +120,14 @@ class InferenceState(object): debug.dbg('execute result: %s in %s', value_set, value) return value_set - @property + @property # type: ignore[misc] @inference_state_function_cache() def builtins_module(self): module_name = 'builtins' builtins_module, = self.import_module((module_name,), sys_path=()) return builtins_module - @property + @property # type: ignore[misc] @inference_state_function_cache() def typing_module(self): typing_module, = self.import_module(('typing',)) diff --git a/jedi/inference/value/module.py b/jedi/inference/value/module.py index b996d814..ac6b2c71 100644 --- a/jedi/inference/value/module.py +++ b/jedi/inference/value/module.py @@ -80,7 +80,7 @@ class ModuleMixin(SubModuleDictMixin): def is_stub(self): return False - @property + @property # type: ignore[misc] @inference_state_method_cache() def name(self): return self._module_name_class(self, self.string_names[-1]) diff --git a/jedi/inference/value/namespace.py b/jedi/inference/value/namespace.py index 0ea9ecf4..fe629a07 100644 --- a/jedi/inference/value/namespace.py +++ b/jedi/inference/value/namespace.py @@ -38,7 +38,7 @@ class ImplicitNamespaceValue(Value, SubModuleDictMixin): def get_qualified_names(self): return () - @property + @property # type: ignore[misc] @inference_state_method_cache() def name(self): string_name = self.py__package__()[-1] From 69be26b16e92ba404d28fd4410f55a2ca321cc3b Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 16:02:36 +0100 Subject: [PATCH 19/74] Change subclass to function wrapper This avoids mypy complaining that we need to provide a generic argument to Popen, which we cannot acctually do as the implementation of Popen does not inherit from typing.Generic. --- .../inference/compiled/subprocess/__init__.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/jedi/inference/compiled/subprocess/__init__.py b/jedi/inference/compiled/subprocess/__init__.py index 6f4097c3..c189c4f3 100644 --- a/jedi/inference/compiled/subprocess/__init__.py +++ b/jedi/inference/compiled/subprocess/__init__.py @@ -29,19 +29,19 @@ _MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py') PICKLE_PROTOCOL = 4 -class _GeneralizedPopen(subprocess.Popen): - def __init__(self, *args, **kwargs): - if os.name == 'nt': - try: - # Was introduced in Python 3.7. - CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW - except AttributeError: - CREATE_NO_WINDOW = 0x08000000 - kwargs['creationflags'] = CREATE_NO_WINDOW - # The child process doesn't need file descriptors except 0, 1, 2. - # This is unix only. - kwargs['close_fds'] = 'posix' in sys.builtin_module_names - super().__init__(*args, **kwargs) +def _GeneralizedPopen(*args, **kwargs): + if os.name == 'nt': + try: + # Was introduced in Python 3.7. + CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW + except AttributeError: + CREATE_NO_WINDOW = 0x08000000 + kwargs['creationflags'] = CREATE_NO_WINDOW + # The child process doesn't need file descriptors except 0, 1, 2. + # This is unix only. + kwargs['close_fds'] = 'posix' in sys.builtin_module_names + + return subprocess.Popen(*args, **kwargs) def _enqueue_output(out, queue_): From 48e5aa777b5e7f086fa95bcf79e021a22fae8b7f Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 16:13:03 +0100 Subject: [PATCH 20/74] Annotate potentially missing import --- jedi/api/keywords.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jedi/api/keywords.py b/jedi/api/keywords.py index a0146de0..80ff13c3 100644 --- a/jedi/api/keywords.py +++ b/jedi/api/keywords.py @@ -1,10 +1,12 @@ import pydoc from contextlib import suppress +from typing import Dict, Optional from jedi.inference.names import AbstractArbitraryName try: - from pydoc_data import topics as pydoc_topics + from pydoc_data import topics + pydoc_topics: Optional[Dict[str, str]] = topics.topics except ImportError: # Python 3.6.8 embeddable does not have pydoc_data. pydoc_topics = None @@ -44,6 +46,6 @@ def imitate_pydoc(string): return '' try: - return pydoc_topics.topics[label].strip() if pydoc_topics else '' + return pydoc_topics[label].strip() if pydoc_topics else '' except KeyError: return '' From 6315709fea290b1d7f4eaa23d3a1c2ebb26aea36 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 19:28:49 +0100 Subject: [PATCH 21/74] Inherit from base class to placate mypy --- jedi/inference/compiled/subprocess/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jedi/inference/compiled/subprocess/__main__.py b/jedi/inference/compiled/subprocess/__main__.py index a9845a38..e1554334 100644 --- a/jedi/inference/compiled/subprocess/__main__.py +++ b/jedi/inference/compiled/subprocess/__main__.py @@ -1,5 +1,6 @@ import os import sys +from importlib.abc import MetaPathFinder from importlib.machinery import PathFinder # Remove the first entry, because it's simply a directory entry that equals @@ -16,7 +17,7 @@ def _get_paths(): return {'jedi': _jedi_path, 'parso': _parso_path} -class _ExactImporter(object): +class _ExactImporter(MetaPathFinder): def __init__(self, path_dct): self._path_dct = path_dct From a2d9fbcd42471ea9416a9aeec2a10d8eb5eda30e Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 19:50:59 +0100 Subject: [PATCH 22/74] Ignore this runtime-only import I've queried this in https://github.com/python/typeshed/issues/4360, though I suspect the answer is going to be to have an ignore comment like this. --- jedi/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/utils.py b/jedi/utils.py index 631cf473..d70cbd16 100644 --- a/jedi/utils.py +++ b/jedi/utils.py @@ -2,7 +2,7 @@ Utilities for end-users. """ -import __main__ +import __main__ # type: ignore[import] from collections import namedtuple import logging import traceback From 4b7e837f0f1264f6a9edc02ac82c15c480cf3de4 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 19:51:12 +0100 Subject: [PATCH 23/74] Configure the package root as implicit exports --- setup.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.cfg b/setup.cfg index 54fd65db..0d413b00 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,6 +47,10 @@ implicit_reexport = False strict_equality = True +[mypy-jedi] +# jedi/__init__.py contains only re-exports. +implicit_reexport = True + [mypy-jedi.debug] # jedi.debug is configured by setting module-level values, which mypy doesn't # know about. From 38f853cf86f79b48bf712d0e8216dae1de9ad325 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 20:03:34 +0100 Subject: [PATCH 24/74] Add ignores for stdlib imports only recently added --- jedi/api/environment.py | 3 ++- jedi/api/keywords.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/jedi/api/environment.py b/jedi/api/environment.py index cd353617..54ad5422 100644 --- a/jedi/api/environment.py +++ b/jedi/api/environment.py @@ -384,7 +384,8 @@ def _get_executable_path(path, safe=True): def _get_executables_from_windows_registry(version): - import winreg + # https://github.com/python/typeshed/pull/3794 adds winreg + import winreg # type: ignore[import] # TODO: support Python Anaconda. sub_keys = [ diff --git a/jedi/api/keywords.py b/jedi/api/keywords.py index 80ff13c3..95c49227 100644 --- a/jedi/api/keywords.py +++ b/jedi/api/keywords.py @@ -5,7 +5,8 @@ from typing import Dict, Optional from jedi.inference.names import AbstractArbitraryName try: - from pydoc_data import topics + # https://github.com/python/typeshed/pull/4351 adds pydoc_data + from pydoc_data import topics # type: ignore[import] pydoc_topics: Optional[Dict[str, str]] = topics.topics except ImportError: # Python 3.6.8 embeddable does not have pydoc_data. From 3f74981d5e3eaf5e49fd9d64b6edb83fc1f0f9fd Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 20:24:53 +0100 Subject: [PATCH 25/74] Also typecheck sith --- .travis.yml | 2 +- sith.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e3b32895..491ab8d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ matrix: script: # Ignore F401, which are unused imports. flake8 is a primitive tool and is sometimes wrong. - 'flake8 --extend-ignore F401 jedi setup.py' - - 'mypy jedi' + - 'mypy jedi sith.py' install: - sudo apt-get -y install python3-venv - pip install .[testing] diff --git a/sith.py b/sith.py index 3b286894..7736ab8a 100755 --- a/sith.py +++ b/sith.py @@ -44,7 +44,7 @@ Options: --pudb Launch pudb when error is raised. """ -from docopt import docopt +from docopt import docopt # type: ignore[import] import json import os From a9cb9fbb1f01e5712361566c7eeaf5c887794088 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Fri, 24 Jul 2020 20:36:22 +0100 Subject: [PATCH 26/74] Give a bit more detail here --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 0d413b00..0186cfe8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,7 +28,8 @@ disallow_any_generics = True disallow_subclassing_any = True -# Ensure that optional types are explicit +# Ensure that optional types are explicit, aiding clarity and ensuring that +# consumers have the choice to enable these if they want to. no_implicit_optional = True strict_optional = True From 9d1587a41d3847213f5d72ab95dfa2aef36b65a6 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 26 Jul 2020 00:11:57 +0200 Subject: [PATCH 27/74] Don't need to inherit from object anymore --- jedi/api/__init__.py | 2 +- jedi/api/classes.py | 2 +- jedi/api/environment.py | 4 ++-- jedi/api/errors.py | 2 +- jedi/api/helpers.py | 2 +- jedi/api/interpreter.py | 2 +- jedi/api/project.py | 2 +- jedi/api/refactoring/__init__.py | 4 ++-- jedi/debug.py | 2 +- jedi/file_io.py | 4 ++-- jedi/inference/__init__.py | 2 +- jedi/inference/analysis.py | 2 +- jedi/inference/arguments.py | 2 +- jedi/inference/base_value.py | 6 +++--- jedi/inference/compiled/access.py | 4 ++-- jedi/inference/compiled/subprocess/__init__.py | 8 ++++---- jedi/inference/compiled/subprocess/__main__.py | 2 +- jedi/inference/compiled/subprocess/functions.py | 2 +- jedi/inference/compiled/value.py | 2 +- jedi/inference/context.py | 4 ++-- jedi/inference/filters.py | 8 ++++---- jedi/inference/flow_analysis.py | 2 +- jedi/inference/gradual/base.py | 4 ++-- jedi/inference/gradual/generics.py | 2 +- jedi/inference/imports.py | 4 ++-- jedi/inference/lazy_value.py | 2 +- jedi/inference/names.py | 10 +++++----- jedi/inference/recursion.py | 4 ++-- jedi/inference/signature.py | 2 +- jedi/inference/utils.py | 2 +- jedi/inference/value/function.py | 2 +- jedi/inference/value/iterable.py | 8 ++++---- jedi/inference/value/klass.py | 2 +- jedi/inference/value/module.py | 2 +- jedi/plugins/__init__.py | 2 +- jedi/utils.py | 2 +- 36 files changed, 59 insertions(+), 59 deletions(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 2be38b8f..a0430e90 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -50,7 +50,7 @@ from jedi.inference.utils import to_list sys.setrecursionlimit(3000) -class Script(object): +class Script: """ A Script is the base for completions, goto or whatever you want to do with Jedi. The counter part of this class is :class:`Interpreter`, which works diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 679d1e59..05ee3885 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -54,7 +54,7 @@ def _values_to_definitions(values): return [Name(c.inference_state, c.name) for c in values] -class BaseName(object): +class BaseName: """ The base class for all definitions, completions and signatures. """ diff --git a/jedi/api/environment.py b/jedi/api/environment.py index cd353617..f7636140 100644 --- a/jedi/api/environment.py +++ b/jedi/api/environment.py @@ -30,7 +30,7 @@ class InvalidPythonEnvironment(Exception): """ -class _BaseEnvironment(object): +class _BaseEnvironment: @memoize_method def get_grammar(self): version_string = '%s.%s' % (self.version_info.major, self.version_info.minor) @@ -121,7 +121,7 @@ class Environment(_BaseEnvironment): return self._get_subprocess().get_sys_path() -class _SameEnvironmentMixin(object): +class _SameEnvironmentMixin: def __init__(self): self._start_executable = self.executable = sys.executable self.path = sys.prefix diff --git a/jedi/api/errors.py b/jedi/api/errors.py index 6dc9ae2d..10cb62af 100644 --- a/jedi/api/errors.py +++ b/jedi/api/errors.py @@ -8,7 +8,7 @@ def parso_to_jedi_errors(grammar, module_node): return [SyntaxError(e) for e in grammar.iter_errors(module_node)] -class SyntaxError(object): +class SyntaxError: """ Syntax errors are generated by :meth:`.Script.get_syntax_errors`. """ diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index 97afb363..98685918 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -203,7 +203,7 @@ def filter_follow_imports(names, follow_builtin_imports=False): yield name -class CallDetails(object): +class CallDetails: def __init__(self, bracket_leaf, children, position): ['bracket_leaf', 'call_index', 'keyword_name_str'] self.bracket_leaf = bracket_leaf diff --git a/jedi/api/interpreter.py b/jedi/api/interpreter.py index d7d573c7..befafe5a 100644 --- a/jedi/api/interpreter.py +++ b/jedi/api/interpreter.py @@ -17,7 +17,7 @@ def _create(inference_state, obj): ) -class NamespaceObject(object): +class NamespaceObject: def __init__(self, dct): self.__dict__ = dct diff --git a/jedi/api/project.py b/jedi/api/project.py index ae00c38b..57b985e0 100644 --- a/jedi/api/project.py +++ b/jedi/api/project.py @@ -56,7 +56,7 @@ def _remove_duplicates_from_path(path): yield p -class Project(object): +class Project: """ Projects are a simple way to manage Python folders and define how Jedi does import resolution. It is mostly used as a parameter to :class:`.Script`. diff --git a/jedi/api/refactoring/__init__.py b/jedi/api/refactoring/__init__.py index 7f8394c9..14c4d910 100644 --- a/jedi/api/refactoring/__init__.py +++ b/jedi/api/refactoring/__init__.py @@ -12,7 +12,7 @@ EXPRESSION_PARTS = ( ).split() -class ChangedFile(object): +class ChangedFile: def __init__(self, inference_state, from_path, to_path, module_node, node_to_str_map): self._inference_state = inference_state @@ -72,7 +72,7 @@ class ChangedFile(object): return '<%s: %s>' % (self.__class__.__name__, self._from_path) -class Refactoring(object): +class Refactoring: def __init__(self, inference_state, file_to_node_changes, renames=()): self._inference_state = inference_state self._renames = renames diff --git a/jedi/debug.py b/jedi/debug.py index 4066cff7..4baa0d6a 100644 --- a/jedi/debug.py +++ b/jedi/debug.py @@ -45,7 +45,7 @@ try: _inited = True except ImportError: - class Fore(object): + class Fore: RED = '' GREEN = '' YELLOW = '' diff --git a/jedi/file_io.py b/jedi/file_io.py index c4518fdb..ead17335 100644 --- a/jedi/file_io.py +++ b/jedi/file_io.py @@ -3,7 +3,7 @@ import os from parso import file_io -class AbstractFolderIO(object): +class AbstractFolderIO: def __init__(self, path): self.path = path @@ -57,7 +57,7 @@ class FolderIO(AbstractFolderIO): del dirs[i] -class FileIOFolderMixin(object): +class FileIOFolderMixin: def get_parent_folder(self): return FolderIO(os.path.dirname(self.path)) diff --git a/jedi/inference/__init__.py b/jedi/inference/__init__.py index 68217365..3461f502 100644 --- a/jedi/inference/__init__.py +++ b/jedi/inference/__init__.py @@ -81,7 +81,7 @@ from jedi.inference.imports import follow_error_node_imports_if_possible from jedi.plugins import plugin_manager -class InferenceState(object): +class InferenceState: def __init__(self, project, environment=None, script_path=None): if environment is None: environment = project.get_environment() diff --git a/jedi/inference/analysis.py b/jedi/inference/analysis.py index 1e51db55..c272a9cb 100644 --- a/jedi/inference/analysis.py +++ b/jedi/inference/analysis.py @@ -26,7 +26,7 @@ CODES = { } -class Error(object): +class Error: def __init__(self, name, module_path, start_pos, message=None): self.path = module_path self._start_pos = start_pos diff --git a/jedi/inference/arguments.py b/jedi/inference/arguments.py index c22d70de..8602f494 100644 --- a/jedi/inference/arguments.py +++ b/jedi/inference/arguments.py @@ -124,7 +124,7 @@ def _parse_argument_clinic(string): allow_kwargs = True -class _AbstractArgumentsMixin(object): +class _AbstractArgumentsMixin: def unpack(self, funcdef=None): raise NotImplementedError diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index c5b2cec0..e51e063c 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -22,7 +22,7 @@ from jedi.cache import memoize_method sentinel = object() -class HelperValueMixin(object): +class HelperValueMixin: def get_root_context(self): value = self if value.parent_context is None: @@ -363,7 +363,7 @@ class TreeValue(Value): return '<%s: %s>' % (self.__class__.__name__, self.tree_node) -class ContextualizedNode(object): +class ContextualizedNode: def __init__(self, context, node): self.context = context self.node = node @@ -405,7 +405,7 @@ def _getitem(value, index_values, contextualized_node): return result -class ValueSet(object): +class ValueSet: def __init__(self, iterable): self._set = frozenset(iterable) for value in iterable: diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index 46dc8249..29f5ced1 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -134,7 +134,7 @@ def load_module(inference_state, dotted_name, sys_path): return create_access_path(inference_state, module) -class AccessPath(object): +class AccessPath: def __init__(self, accesses): self.accesses = accesses @@ -156,7 +156,7 @@ def get_api_type(obj): return 'instance' -class DirectObjectAccess(object): +class DirectObjectAccess: def __init__(self, inference_state, obj): self._inference_state = inference_state self._obj = obj diff --git a/jedi/inference/compiled/subprocess/__init__.py b/jedi/inference/compiled/subprocess/__init__.py index 6f4097c3..13366278 100644 --- a/jedi/inference/compiled/subprocess/__init__.py +++ b/jedi/inference/compiled/subprocess/__init__.py @@ -81,7 +81,7 @@ def _cleanup_process(process, thread): pass -class _InferenceStateProcess(object): +class _InferenceStateProcess: def __init__(self, inference_state): self._inference_state_weakref = weakref.ref(inference_state) self._inference_state_id = id(inference_state) @@ -162,7 +162,7 @@ class InferenceStateSubprocess(_InferenceStateProcess): self._compiled_subprocess.delete_inference_state(self._inference_state_id) -class CompiledSubprocess(object): +class CompiledSubprocess: is_crashed = False def __init__(self, executable, env_vars=None): @@ -280,7 +280,7 @@ class CompiledSubprocess(object): self._inference_state_deletion_queue.append(inference_state_id) -class Listener(object): +class Listener: def __init__(self): self._inference_states = {} # TODO refactor so we don't need to process anymore just handle @@ -346,7 +346,7 @@ class Listener(object): pickle_dump(result, stdout, PICKLE_PROTOCOL) -class AccessHandle(object): +class AccessHandle: def __init__(self, subprocess, access, id_): self.access = access self._subprocess = subprocess diff --git a/jedi/inference/compiled/subprocess/__main__.py b/jedi/inference/compiled/subprocess/__main__.py index a9845a38..370e2361 100644 --- a/jedi/inference/compiled/subprocess/__main__.py +++ b/jedi/inference/compiled/subprocess/__main__.py @@ -16,7 +16,7 @@ def _get_paths(): return {'jedi': _jedi_path, 'parso': _parso_path} -class _ExactImporter(object): +class _ExactImporter: def __init__(self, path_dct): self._path_dct = path_dct diff --git a/jedi/inference/compiled/subprocess/functions.py b/jedi/inference/compiled/subprocess/functions.py index 2d3ab607..9aa1cb2e 100644 --- a/jedi/inference/compiled/subprocess/functions.py +++ b/jedi/inference/compiled/subprocess/functions.py @@ -229,7 +229,7 @@ def _get_source(loader, fullname): name=fullname) -class ImplicitNSInfo(object): +class ImplicitNSInfo: """Stores information returned from an implicit namespace spec""" def __init__(self, name, paths): self.name = name diff --git a/jedi/inference/compiled/value.py b/jedi/inference/compiled/value.py index 6802e43d..1b34b123 100644 --- a/jedi/inference/compiled/value.py +++ b/jedi/inference/compiled/value.py @@ -22,7 +22,7 @@ from jedi.inference.signature import BuiltinSignature from jedi.inference.context import CompiledContext, CompiledModuleContext -class CheckAttribute(object): +class CheckAttribute: """Raises :exc:`AttributeError` if the attribute X is not available.""" def __init__(self, check_name=None): # Remove the py in front of e.g. py__call__. diff --git a/jedi/inference/context.py b/jedi/inference/context.py index 951f3bf6..cbbce0c7 100644 --- a/jedi/inference/context.py +++ b/jedi/inference/context.py @@ -13,7 +13,7 @@ from jedi import debug from jedi import parser_utils -class AbstractContext(object): +class AbstractContext: # Must be defined: inference_state and tree_node and parent_context as an attribute/property def __init__(self, inference_state): @@ -216,7 +216,7 @@ class ValueContext(AbstractContext): return '%s(%s)' % (self.__class__.__name__, self._value) -class TreeContextMixin(object): +class TreeContextMixin: def infer_node(self, node): from jedi.inference.syntax_tree import infer_node return infer_node(self, node) diff --git a/jedi/inference/filters.py b/jedi/inference/filters.py index 6551cebc..a796c445 100644 --- a/jedi/inference/filters.py +++ b/jedi/inference/filters.py @@ -18,7 +18,7 @@ from jedi.inference.names import TreeNameDefinition, ParamName, \ _definition_name_cache = weakref.WeakKeyDictionary() -class AbstractFilter(object): +class AbstractFilter: _until_position = None def _filter(self, names): @@ -35,7 +35,7 @@ class AbstractFilter(object): raise NotImplementedError -class FilterWrapper(object): +class FilterWrapper: name_wrapper_class = None def __init__(self, wrapped_filter): @@ -229,7 +229,7 @@ class DictFilter(AbstractFilter): return '<%s: for {%s}>' % (self.__class__.__name__, keys) -class MergedFilter(object): +class MergedFilter: def __init__(self, *filters): self._filters = filters @@ -320,7 +320,7 @@ class _OverwriteMeta(type): cls.overwritten_methods = base_dct -class _AttributeOverwriteMixin(object): +class _AttributeOverwriteMixin: def get_filters(self, *args, **kwargs): yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value) yield from self._wrapped_value.get_filters(*args, **kwargs) diff --git a/jedi/inference/flow_analysis.py b/jedi/inference/flow_analysis.py index 184f367f..d5369711 100644 --- a/jedi/inference/flow_analysis.py +++ b/jedi/inference/flow_analysis.py @@ -3,7 +3,7 @@ from jedi.inference.recursion import execution_allowed from jedi.inference.helpers import is_big_annoying_library -class Status(object): +class Status: lookup_table = {} def __init__(self, value, name): diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index 22c204a2..fb4740d3 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -37,7 +37,7 @@ class _BoundTypeVarName(AbstractNameDefinition): return '<%s %s -> %s>' % (self.__class__.__name__, self.py__name__(), self._value_set) -class _TypeVarFilter(object): +class _TypeVarFilter: """ A filter for all given variables in a class. @@ -246,7 +246,7 @@ class GenericClass(DefineGenericBaseClass, ClassMixin): return type_var_dict -class _LazyGenericBaseClass(object): +class _LazyGenericBaseClass: def __init__(self, class_value, lazy_base_class, generics_manager): self._class_value = class_value self._lazy_base_class = lazy_base_class diff --git a/jedi/inference/gradual/generics.py b/jedi/inference/gradual/generics.py index 6bd6aa63..f4a5ae9c 100644 --- a/jedi/inference/gradual/generics.py +++ b/jedi/inference/gradual/generics.py @@ -23,7 +23,7 @@ def _resolve_forward_references(context, value_set): yield value -class _AbstractGenericManager(object): +class _AbstractGenericManager: def get_index_and_execute(self, index): try: return self[index].execute_annotation() diff --git a/jedi/inference/imports.py b/jedi/inference/imports.py index 8de3fe84..5ae5819f 100644 --- a/jedi/inference/imports.py +++ b/jedi/inference/imports.py @@ -32,7 +32,7 @@ from jedi.inference.compiled.subprocess.functions import ImplicitNSInfo from jedi.plugins import plugin_manager -class ModuleCache(object): +class ModuleCache: def __init__(self): self._name_cache = {} @@ -150,7 +150,7 @@ def _level_to_base_import_path(project_path, directory, level): return None, directory -class Importer(object): +class Importer: def __init__(self, inference_state, import_path, module_context, level=0): """ An implementation similar to ``__import__``. Use `follow` diff --git a/jedi/inference/lazy_value.py b/jedi/inference/lazy_value.py index 0ece8690..b149f21e 100644 --- a/jedi/inference/lazy_value.py +++ b/jedi/inference/lazy_value.py @@ -2,7 +2,7 @@ from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.common import monkeypatch -class AbstractLazyValue(object): +class AbstractLazyValue: def __init__(self, data, min=1, max=1): self.data = data self.min = min diff --git a/jedi/inference/names.py b/jedi/inference/names.py index 81161359..02d75ea4 100644 --- a/jedi/inference/names.py +++ b/jedi/inference/names.py @@ -23,7 +23,7 @@ def _merge_name_docs(names): return doc -class AbstractNameDefinition(object): +class AbstractNameDefinition: start_pos = None string_name = None parent_context = None @@ -223,7 +223,7 @@ class AbstractTreeName(AbstractNameDefinition): return self.tree_name.start_pos -class ValueNameMixin(object): +class ValueNameMixin: def infer(self): return ValueSet([self._value]) @@ -346,7 +346,7 @@ class TreeNameDefinition(AbstractTreeName): return '' -class _ParamMixin(object): +class _ParamMixin: def maybe_positional_argument(self, include_star=True): options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD] if include_star: @@ -604,7 +604,7 @@ class SubModuleName(ImportName): _level = 1 -class NameWrapper(object): +class NameWrapper: def __init__(self, wrapped_name): self._wrapped_name = wrapped_name @@ -615,7 +615,7 @@ class NameWrapper(object): return '%s(%s)' % (self.__class__.__name__, self._wrapped_name) -class StubNameMixin(object): +class StubNameMixin: def py__doc__(self): from jedi.inference.gradual.conversion import convert_names # Stubs are not complicated and we can just follow simple statements diff --git a/jedi/inference/recursion.py b/jedi/inference/recursion.py index a0897fa8..a2f99c52 100644 --- a/jedi/inference/recursion.py +++ b/jedi/inference/recursion.py @@ -50,7 +50,7 @@ A function may not be executed more than this number of times recursively. """ -class RecursionDetector(object): +class RecursionDetector: def __init__(self): self.pushed_nodes = [] @@ -92,7 +92,7 @@ def execution_recursion_decorator(default=NO_VALUES): return decorator -class ExecutionRecursionDetector(object): +class ExecutionRecursionDetector: """ Catches recursions of executions. """ diff --git a/jedi/inference/signature.py b/jedi/inference/signature.py index 5f203f79..565a269b 100644 --- a/jedi/inference/signature.py +++ b/jedi/inference/signature.py @@ -5,7 +5,7 @@ from jedi import debug from jedi import parser_utils -class _SignatureMixin(object): +class _SignatureMixin: def to_string(self): def param_strings(): is_positional = False diff --git a/jedi/inference/utils.py b/jedi/inference/utils.py index e4c66537..ab10bcd9 100644 --- a/jedi/inference/utils.py +++ b/jedi/inference/utils.py @@ -70,7 +70,7 @@ def reraise_uncaught(func): return wrapper -class PushBackIterator(object): +class PushBackIterator: def __init__(self, iterator): self.pushes = [] self.iterator = iterator diff --git a/jedi/inference/value/function.py b/jedi/inference/value/function.py index 967ee6c0..2471a065 100644 --- a/jedi/inference/value/function.py +++ b/jedi/inference/value/function.py @@ -53,7 +53,7 @@ class FunctionAndClassBase(TreeValue): return None -class FunctionMixin(object): +class FunctionMixin: api_type = 'function' def get_filters(self, origin_scope=None): diff --git a/jedi/inference/value/iterable.py b/jedi/inference/value/iterable.py index 18611a56..2f970fe8 100644 --- a/jedi/inference/value/iterable.py +++ b/jedi/inference/value/iterable.py @@ -19,7 +19,7 @@ from jedi.inference.context import CompForContext from jedi.inference.value.dynamic_arrays import check_array_additions -class IterableMixin(object): +class IterableMixin: def py__next__(self, contextualized_node=None): return self.py__iter__(contextualized_node) @@ -127,7 +127,7 @@ def comprehension_from_atom(inference_state, value, atom): ) -class ComprehensionMixin(object): +class ComprehensionMixin: @inference_state_method_cache() def _get_comp_for_context(self, parent_context, comp_for): return CompForContext(parent_context, comp_for) @@ -175,7 +175,7 @@ class ComprehensionMixin(object): return "<%s of %s>" % (type(self).__name__, self._sync_comp_for_node) -class _DictMixin(object): +class _DictMixin: def _get_generics(self): return tuple(c_set.py__class__() for c_set in self.get_mapping_item_values()) @@ -247,7 +247,7 @@ class GeneratorComprehension(_BaseComprehension, GeneratorBase): pass -class _DictKeyMixin(object): +class _DictKeyMixin: # TODO merge with _DictMixin? def get_mapping_item_values(self): return self._dict_keys(), self._dict_values() diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index 79d070ba..0135624a 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -142,7 +142,7 @@ class ClassFilter(ParserTreeFilter): return [name for name in names if self._access_possible(name)] -class ClassMixin(object): +class ClassMixin: def is_class(self): return True diff --git a/jedi/inference/value/module.py b/jedi/inference/value/module.py index 4de1e621..128f7185 100644 --- a/jedi/inference/value/module.py +++ b/jedi/inference/value/module.py @@ -33,7 +33,7 @@ class _ModuleAttributeName(AbstractNameDefinition): return compiled.get_string_value_set(self.parent_context.inference_state) -class SubModuleDictMixin(object): +class SubModuleDictMixin: @inference_state_method_cache() def sub_modules_dict(self): """ diff --git a/jedi/plugins/__init__.py b/jedi/plugins/__init__.py index 23588bd4..8067676d 100644 --- a/jedi/plugins/__init__.py +++ b/jedi/plugins/__init__.py @@ -1,7 +1,7 @@ from functools import wraps -class _PluginManager(object): +class _PluginManager: def __init__(self): self._registered_plugins = [] self._cached_base_callbacks = {} diff --git a/jedi/utils.py b/jedi/utils.py index 631cf473..dd529be7 100644 --- a/jedi/utils.py +++ b/jedi/utils.py @@ -65,7 +65,7 @@ def setup_readline(namespace_module=__main__, fuzzy=False): level=logging.DEBUG ) - class JediRL(object): + class JediRL: def complete(self, text, state): """ This complete stuff is pretty weird, a generator would make From b3edda30c4277ee932cd7cd6b458dc7b9e143367 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 26 Jul 2020 12:09:04 +0100 Subject: [PATCH 28/74] Explain why we 'type: ignore' these properties --- jedi/inference/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jedi/inference/__init__.py b/jedi/inference/__init__.py index fc2bf0c3..5d5e4357 100644 --- a/jedi/inference/__init__.py +++ b/jedi/inference/__init__.py @@ -120,6 +120,7 @@ class InferenceState(object): debug.dbg('execute result: %s in %s', value_set, value) return value_set + # mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362) @property # type: ignore[misc] @inference_state_function_cache() def builtins_module(self): From 86e0e16625a4c5916ad40e5a01cc00e6a7d75cef Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 26 Jul 2020 12:10:59 +0100 Subject: [PATCH 29/74] Drop redundant rtype comment This is better expressed as an annotation. --- jedi/api/classes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 5c746cd9..2cab497a 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -96,8 +96,6 @@ class BaseName(object): def module_path(self) -> Optional[Path]: """ Shows the file path of a module. e.g. ``/usr/lib/python3.9/os.py`` - - :rtype: Path or None """ module = self._get_module_context() if module.is_stub() or not module.is_compiled(): From 0571e126170786b02edca18d7776d2f6bf72d83e Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 26 Jul 2020 12:11:34 +0100 Subject: [PATCH 30/74] These attributes aren't optional They just don't yet have a value. --- jedi/inference/filters.py | 2 +- jedi/inference/names.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/inference/filters.py b/jedi/inference/filters.py index 05a9efcf..ba3d67c9 100644 --- a/jedi/inference/filters.py +++ b/jedi/inference/filters.py @@ -39,7 +39,7 @@ class AbstractFilter(object): class FilterWrapper(object): - name_wrapper_class: Optional[Type[NameWrapper]] = None + name_wrapper_class: Type[NameWrapper] def __init__(self, wrapped_filter): self._wrapped_filter = wrapped_filter diff --git a/jedi/inference/names.py b/jedi/inference/names.py index 1d20ba5f..12550982 100644 --- a/jedi/inference/names.py +++ b/jedi/inference/names.py @@ -26,7 +26,7 @@ def _merge_name_docs(names): class AbstractNameDefinition(object): start_pos: Optional[Tuple[int, int]] = None - string_name: Optional[str] = None + string_name: str parent_context = None tree_name = None is_value_name = True From 45c90efb5c5001a3ec831ddfc5b1b66f6acb2ffb Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 26 Jul 2020 12:17:54 +0100 Subject: [PATCH 31/74] Remove a couple of unused imports --- jedi/inference/filters.py | 2 +- jedi/inference/gradual/utils.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/jedi/inference/filters.py b/jedi/inference/filters.py index ba3d67c9..9bdc4c23 100644 --- a/jedi/inference/filters.py +++ b/jedi/inference/filters.py @@ -3,7 +3,7 @@ Filters are objects that you can use to filter names in different scopes. They are needed for name resolution. """ from abc import abstractmethod -from typing import List, Optional, MutableMapping, Type +from typing import List, MutableMapping, Type import weakref from parso.tree import search_ancestor diff --git a/jedi/inference/gradual/utils.py b/jedi/inference/gradual/utils.py index 83162625..1fc90e7f 100644 --- a/jedi/inference/gradual/utils.py +++ b/jedi/inference/gradual/utils.py @@ -1,4 +1,3 @@ -import os from pathlib import Path from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module From cefc363f64a32f63498bc594d5154f0ca3be09bf Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 26 Jul 2020 12:20:08 +0100 Subject: [PATCH 32/74] Configure mypy and flake8 for our re-export files This removes the need to use __all__ in these files, while also allowing us to have strictness elsewhere in the codebase. --- .travis.yml | 3 +-- jedi/inference/compiled/__init__.py | 19 +++---------------- jedi/inference/value/__init__.py | 18 +++--------------- setup.cfg | 10 ++++++++-- 4 files changed, 15 insertions(+), 35 deletions(-) diff --git a/.travis.yml b/.travis.yml index 491ab8d3..1588ee1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,8 +30,7 @@ matrix: install: - 'pip install .[qa]' script: - # Ignore F401, which are unused imports. flake8 is a primitive tool and is sometimes wrong. - - 'flake8 --extend-ignore F401 jedi setup.py' + - 'flake8 jedi setup.py' - 'mypy jedi sith.py' install: - sudo apt-get -y install python3-venv diff --git a/jedi/inference/compiled/__init__.py b/jedi/inference/compiled/__init__.py index f4a8691e..09ac19f9 100644 --- a/jedi/inference/compiled/__init__.py +++ b/jedi/inference/compiled/__init__.py @@ -1,23 +1,10 @@ +# This file also re-exports symbols for wider use. We configure mypy and flake8 +# to be aware that this file does this. + from jedi.inference.compiled.value import CompiledValue, CompiledName, \ CompiledValueFilter, CompiledValueName, create_from_access_path from jedi.inference.base_value import LazyValueWrapper -__all__ = ( - 'CompiledValue', - 'CompiledName', - 'CompiledValueFilter', - 'CompiledValueName', - 'create_from_access_path', - - 'LazyValueWrapper', - - 'builtin_from_name', - 'ExactValue', - 'create_simple_object', - 'get_string_value_set', - 'load_module', -) - def builtin_from_name(inference_state, string): typing_builtins_module = inference_state.builtins_module diff --git a/jedi/inference/value/__init__.py b/jedi/inference/value/__init__.py index d35f0647..62164215 100644 --- a/jedi/inference/value/__init__.py +++ b/jedi/inference/value/__init__.py @@ -1,21 +1,9 @@ +# Re-export symbols for wider use. We configure mypy and flake8 to be aware that +# this file does this. + from jedi.inference.value.module import ModuleValue from jedi.inference.value.klass import ClassValue from jedi.inference.value.function import FunctionValue, \ MethodValue from jedi.inference.value.instance import AnonymousInstance, BoundMethod, \ CompiledInstance, AbstractInstanceValue, TreeInstance - -__all__ = ( - 'ModuleValue', - - 'ClassValue', - - 'FunctionValue', - 'MethodValue', - - 'AnonymousInstance', - 'BoundMethod', - 'CompiledInstance', - 'AbstractInstanceValue', - 'TreeInstance', -) diff --git a/setup.cfg b/setup.cfg index 0186cfe8..bc1f1399 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,12 @@ ignore = W503, # Single letter loop variables are often fine E741, +per-file-ignores = + # Ignore apparently unused imports in files where we're (implicitly) + # re-exporting them. + jedi/__init__.py:F401 + jedi/inference/compiled/__init__.py:F401 + jedi/inference/value/__init__.py:F401 exclude = jedi/third_party/* .tox/* [pycodestyle] @@ -48,8 +54,8 @@ implicit_reexport = False strict_equality = True -[mypy-jedi] -# jedi/__init__.py contains only re-exports. +[mypy-jedi,jedi.inference.compiled,jedi.inference.value] +# Various __init__.py files which contain re-exports we want to implicitly make. implicit_reexport = True [mypy-jedi.debug] From 19b8eaea59503a817ac4c072b12fc0a96a4cca92 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 26 Jul 2020 13:26:14 +0100 Subject: [PATCH 33/74] Link mypy issue --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index bc1f1399..0c78a591 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,5 +60,5 @@ implicit_reexport = True [mypy-jedi.debug] # jedi.debug is configured by setting module-level values, which mypy doesn't -# know about. +# know about. See https://github.com/python/mypy/issues/9209. warn_unreachable = False From 6364dd1511fde7b57f7a63b88373a30bc1cb2b6d Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 26 Jul 2020 14:43:41 +0100 Subject: [PATCH 34/74] Add explicit Optional annotation This isn't a mypy issue -- there's no way it could otherwise know that this `None` value is in fact an optional callable. --- jedi/debug.py | 3 ++- setup.cfg | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/jedi/debug.py b/jedi/debug.py index 72259842..2b2bcfb8 100644 --- a/jedi/debug.py +++ b/jedi/debug.py @@ -1,6 +1,7 @@ import os import time from contextlib import contextmanager +from typing import Callable, Optional _inited = False @@ -62,7 +63,7 @@ enable_warning = False enable_notice = False # callback, interface: level, str -debug_function = None +debug_function: Optional[Callable[[str, str], None]] = None _debug_indent = 0 _start_time = time.time() diff --git a/setup.cfg b/setup.cfg index 0c78a591..a9379444 100644 --- a/setup.cfg +++ b/setup.cfg @@ -57,8 +57,3 @@ strict_equality = True [mypy-jedi,jedi.inference.compiled,jedi.inference.value] # Various __init__.py files which contain re-exports we want to implicitly make. implicit_reexport = True - -[mypy-jedi.debug] -# jedi.debug is configured by setting module-level values, which mypy doesn't -# know about. See https://github.com/python/mypy/issues/9209. -warn_unreachable = False From 20be4f02c88100c08c571addd40b31dcea4b8e4e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 27 Jul 2020 11:28:05 -0700 Subject: [PATCH 35/74] Turn print into warning to simplify silencing them. --- jedi/inference/compiled/access.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index 29f5ced1..299e3f03 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -117,13 +117,13 @@ def load_module(inference_state, dotted_name, sys_path): __import__(dotted_name) except ImportError: # If a module is "corrupt" or not really a Python module or whatever. - print('Module %s not importable in path %s.' % (dotted_name, sys_path), file=sys.stderr) + warnings.warn('Module %s not importable in path %s.' % (dotted_name, sys_path), UserWarning) return None except Exception: # Since __import__ pretty much makes code execution possible, just # catch any error here and print it. import traceback - print("Cannot import:\n%s" % traceback.format_exc(), file=sys.stderr) + warnings.warn("Cannot import:\n%s" % traceback.format_exc(), UserWarning) return None finally: sys.path = temp From 2f7d0ec42cfff75105b84fea0d1b6517842969fc Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 1 Aug 2020 18:26:26 +0200 Subject: [PATCH 36/74] Project attributes are now read accessible --- CHANGELOG.rst | 1 + jedi/api/project.py | 23 +++++++++++++++++++++++ test/test_api/test_project.py | 7 ++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a1b2d301..de8e0bf2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,7 @@ Unreleased - Functions with ``@property`` now return ``property`` instead of ``function`` in ``Name().type`` - Started using annotations +- Project attributes are now read accessible This is likely going to be the last minor release before 1.0. diff --git a/jedi/api/project.py b/jedi/api/project.py index 57b985e0..41554314 100644 --- a/jedi/api/project.py +++ b/jedi/api/project.py @@ -152,6 +152,29 @@ class Project: """ return self._path + @property + def sys_path(self): + """ + The sys path provided to this project. This can be None and in that + case will be auto generated. + """ + return self._sys_path + + @property + def smart_sys_path(self): + """ + If the sys path is going to be calculated in a smart way, where + additional paths are added. + """ + return self._smart_sys_path + + @property + def load_unsafe_extensions(self): + """ + Wheter the project loads unsafe extensions. + """ + return self._load_unsafe_extensions + @inference_state_as_method_param_cache() def _get_base_sys_path(self, inference_state): # The sys path has not been set explicitly. diff --git a/test/test_api/test_project.py b/test/test_api/test_project.py index 7ec880ad..c8533618 100644 --- a/test/test_api/test_project.py +++ b/test/test_api/test_project.py @@ -17,7 +17,12 @@ def test_django_default_project(Script): ) c, = script.complete() assert c.name == "SomeModel" - assert script._inference_state.project._django is True + + project = script._inference_state.project + assert project._django is True + assert project.sys_path is None + assert project.smart_sys_path is True + assert project.load_unsafe_extensions is False def test_django_default_project_of_file(Script): From a9e2cd5a74b3eea91ad2a866ff8152b42fc509c8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 3 Aug 2020 08:24:24 -0700 Subject: [PATCH 37/74] Reformat and move imports to top level. --- jedi/inference/compiled/access.py | 12 +++++++++--- jedi/third_party/django-stubs | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index 299e3f03..42fe7ebe 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -1,5 +1,6 @@ import inspect import types +import traceback import sys import operator as op from collections import namedtuple @@ -117,13 +118,18 @@ def load_module(inference_state, dotted_name, sys_path): __import__(dotted_name) except ImportError: # If a module is "corrupt" or not really a Python module or whatever. - warnings.warn('Module %s not importable in path %s.' % (dotted_name, sys_path), UserWarning) + warnings.warn( + "Module %s not importable in path %s." % (dotted_name, sys_path), + UserWarning, + stacklevel=2, + ) return None except Exception: # Since __import__ pretty much makes code execution possible, just # catch any error here and print it. - import traceback - warnings.warn("Cannot import:\n%s" % traceback.format_exc(), UserWarning) + warnings.warn( + "Cannot import:\n%s" % traceback.format_exc(), UserWarning, stacklevel=2 + ) return None finally: sys.path = temp diff --git a/jedi/third_party/django-stubs b/jedi/third_party/django-stubs index fd057010..92c8dfc9 160000 --- a/jedi/third_party/django-stubs +++ b/jedi/third_party/django-stubs @@ -1 +1 @@ -Subproject commit fd057010f6cbf176f57d1099e82be46d39b99cb9 +Subproject commit 92c8dfc93f840b936e33eb3f1770293627ac0f15 From cce3ecb1e45fe0c9ab0ff4d1e1e08de501c6ec56 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 4 Aug 2020 21:49:42 +0100 Subject: [PATCH 38/74] Use the default handling of optionals This is strict handling, but allows implicit declarations. --- setup.cfg | 5 ----- 1 file changed, 5 deletions(-) diff --git a/setup.cfg b/setup.cfg index a9379444..904b5ffe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,11 +34,6 @@ disallow_any_generics = True disallow_subclassing_any = True -# Ensure that optional types are explicit, aiding clarity and ensuring that -# consumers have the choice to enable these if they want to. -no_implicit_optional = True -strict_optional = True - # Avoid creating future gotchas emerging from bad typing warn_redundant_casts = True warn_unused_ignores = True From 94bf83c8265e928a7849253bcfb72bb8f7e1908b Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 5 Aug 2020 00:18:24 +0200 Subject: [PATCH 39/74] Revert Django changes in a9e2cd5a74b3eea91ad2a866ff8152b42fc509c8 This was probably an accident in #1646 --- jedi/third_party/django-stubs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/third_party/django-stubs b/jedi/third_party/django-stubs index 92c8dfc9..fd057010 160000 --- a/jedi/third_party/django-stubs +++ b/jedi/third_party/django-stubs @@ -1 +1 @@ -Subproject commit 92c8dfc93f840b936e33eb3f1770293627ac0f15 +Subproject commit fd057010f6cbf176f57d1099e82be46d39b99cb9 From f12ed2088a93d1e850e5e7c92c6bad6df4db822a Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 5 Aug 2020 00:52:50 +0200 Subject: [PATCH 40/74] Use pathlib for file ios, because the new parso is out --- jedi/inference/compiled/mixed.py | 4 +++- jedi/inference/compiled/subprocess/functions.py | 3 ++- jedi/inference/references.py | 4 ++-- jedi/inference/sys_path.py | 2 +- test/test_api/test_api.py | 8 ++++---- test/test_inference/test_imports.py | 10 +++++----- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/jedi/inference/compiled/mixed.py b/jedi/inference/compiled/mixed.py index e96957ed..86675794 100644 --- a/jedi/inference/compiled/mixed.py +++ b/jedi/inference/compiled/mixed.py @@ -4,6 +4,7 @@ Used only for REPL Completion. import inspect import os +from pathlib import Path from jedi.parser_utils import get_cached_code_lines @@ -190,7 +191,8 @@ def _find_syntax_node_name(inference_state, python_object): except TypeError: # The type might not be known (e.g. class_with_dict.__weakref__) return None - if path is None or not os.path.exists(path): + path = None if path is None else Path(path) + if path is None or not path.exists(): # The path might not exist or be e.g. . return None diff --git a/jedi/inference/compiled/subprocess/functions.py b/jedi/inference/compiled/subprocess/functions.py index 9aa1cb2e..601fe6c2 100644 --- a/jedi/inference/compiled/subprocess/functions.py +++ b/jedi/inference/compiled/subprocess/functions.py @@ -3,6 +3,7 @@ import os import inspect import importlib import warnings +from pathlib import Path from zipimport import zipimporter from importlib.machinery import all_suffixes @@ -211,7 +212,7 @@ def _from_loader(loader, string): if code is None: return None, is_package if isinstance(loader, zipimporter): - return ZipFileIO(module_path, code, cast_path(loader.archive)), is_package + return ZipFileIO(module_path, code, Path(cast_path(loader.archive))), is_package return KnownContentFileIO(module_path, code), is_package diff --git a/jedi/inference/references.py b/jedi/inference/references.py index e1b97c41..07a840a1 100644 --- a/jedi/inference/references.py +++ b/jedi/inference/references.py @@ -203,11 +203,11 @@ def recurse_find_python_folders_and_files(folder_io, except_paths=()): # Delete folders that we don't want to iterate over. for file_io in file_ios: path = file_io.path - if path.endswith('.py') or path.endswith('.pyi'): + if path.suffix in ('.py', '.pyi'): if path not in except_paths: yield None, file_io - if path.endswith('.gitignore'): + if path.name == '.gitignore': ignored_paths, ignored_names = \ gitignored_lines(root_folder_io, file_io) except_paths |= ignored_paths diff --git a/jedi/inference/sys_path.py b/jedi/inference/sys_path.py index b7457e88..0f44be4a 100644 --- a/jedi/inference/sys_path.py +++ b/jedi/inference/sys_path.py @@ -164,7 +164,7 @@ def _get_paths_from_buildout_script(inference_state, buildout_script_path): inference_state, module_node, file_io=file_io, string_names=None, - code_lines=get_cached_code_lines(inference_state.grammar, str(buildout_script_path)), + code_lines=get_cached_code_lines(inference_state.grammar, buildout_script_path), ).as_context() yield from check_sys_path_modifications(module_context) diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index c86be78c..1829e30f 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -15,7 +15,7 @@ from test.helpers import test_dir, get_example_dir def test_preload_modules(): - def check_loaded(*modules): + def check_loaded(*module_names): for grammar_cache in cache.parser_cache.values(): if None in grammar_cache: break @@ -25,9 +25,9 @@ def test_preload_modules(): if path is not None and str(path).startswith(str(typeshed.TYPESHED_PATH)) ) # +1 for None module (currently used) - assert len(grammar_cache) - typeshed_cache_count == len(modules) + 1 - for i in modules: - assert [i in k for k in grammar_cache.keys() if k is not None] + assert len(grammar_cache) - typeshed_cache_count == len(module_names) + 1 + for i in module_names: + assert [i in str(k) for k in grammar_cache.keys() if k is not None] old_cache = cache.parser_cache.copy() cache.parser_cache.clear() diff --git a/test/test_inference/test_imports.py b/test/test_inference/test_imports.py index 32acf093..f1eb3f57 100644 --- a/test/test_inference/test_imports.py +++ b/test/test_inference/test_imports.py @@ -29,13 +29,13 @@ def test_find_module_basic(): def test_find_module_package(): file_io, is_package = _find_module('json') - assert file_io.path.endswith(os.path.join('json', '__init__.py')) + assert file_io.path.parts[-2:] == ('json', '__init__.py') assert is_package is True def test_find_module_not_package(): file_io, is_package = _find_module('io') - assert file_io.path.endswith('io.py') + assert file_io.path.name == 'io.py' assert is_package is False @@ -55,8 +55,8 @@ def test_find_module_package_zipped(Script, inference_state, environment): full_name='pkg' ) assert file_io is not None - assert file_io.path.endswith(os.path.join('pkg.zip', 'pkg', '__init__.py')) - assert file_io._zip_path.endswith('pkg.zip') + assert file_io.path.parts[-3:] == ('pkg.zip', 'pkg', '__init__.py') + assert file_io._zip_path.name == 'pkg.zip' assert is_package is True @@ -108,7 +108,7 @@ def test_find_module_not_package_zipped(Script, inference_state, environment): string='not_pkg', full_name='not_pkg' ) - assert file_io.path.endswith(os.path.join('not_pkg.zip', 'not_pkg.py')) + assert file_io.path.parts[-2:] == ('not_pkg.zip', 'not_pkg.py') assert is_package is False From 209e2713fd699b8a54aa4c8bbd0915e6c51f2092 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 5 Aug 2020 00:55:50 +0200 Subject: [PATCH 41/74] Remove the requirements file and require latest parso --- MANIFEST.in | 1 - requirements.txt | 1 - setup.py | 4 +--- 3 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 requirements.txt diff --git a/MANIFEST.in b/MANIFEST.in index d6387b17..75a895c6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,7 +6,6 @@ include .coveragerc include sith.py include conftest.py include pytest.ini -include requirements.txt recursive-include jedi/third_party *.pyi include jedi/third_party/typeshed/LICENSE include jedi/third_party/django-stubs/LICENSE.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index eb169cbe..00000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -parso>=0.7.0,<0.8.0 diff --git a/setup.py b/setup.py index bf9be8c2..041ae0b2 100755 --- a/setup.py +++ b/setup.py @@ -12,8 +12,6 @@ __AUTHOR_EMAIL__ = 'davidhalter88@gmail.com' version = get_module_constant("jedi", "__version__") readme = open('README.rst').read() + '\n\n' + open('CHANGELOG.rst').read() -with open('requirements.txt') as f: - install_requires = f.read().splitlines() assert os.path.isfile("jedi/third_party/typeshed/LICENSE"), \ "Please download the typeshed submodule first (Hint: git submodule update --init)" @@ -34,7 +32,7 @@ setup(name='jedi', long_description=readme, packages=find_packages(exclude=['test', 'test.*']), python_requires='>=3.6', - install_requires=install_requires, + install_requires=['parso>=0.8.0,<0.9.0'], extras_require={ 'testing': [ 'pytest<6.0.0', From abf63d73d38f50e6d5a7ab36c371e1bb8e9e6fa9 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 5 Aug 2020 10:12:47 +0200 Subject: [PATCH 42/74] Basic implementation support for namedexpr, fixes #1647 --- CHANGELOG.rst | 1 + jedi/inference/__init__.py | 2 ++ jedi/inference/syntax_tree.py | 2 ++ jedi/parser_utils.py | 12 +++++++-- test/completion/flow_analysis.py | 2 +- test/completion/named_expression.py | 39 +++++++++++++++++++++++++++++ 6 files changed, 55 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index de8e0bf2..f288f8f4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,7 @@ Unreleased - Functions with ``@property`` now return ``property`` instead of ``function`` in ``Name().type`` - Started using annotations +- Better support for the walrus operator - Project attributes are now read accessible This is likely going to be the last minor release before 1.0. diff --git a/jedi/inference/__init__.py b/jedi/inference/__init__.py index 8cf6a1c2..7409aa84 100644 --- a/jedi/inference/__init__.py +++ b/jedi/inference/__init__.py @@ -170,6 +170,8 @@ class InferenceState: return tree_name_to_values(self, context, name) elif type_ == 'param': return context.py__getattribute__(name.value, position=name.end_pos) + elif type_ == 'namedexpr_test': + return context.infer_node(def_) else: result = follow_error_node_imports_if_possible(context, name) if result is not None: diff --git a/jedi/inference/syntax_tree.py b/jedi/inference/syntax_tree.py index fb7743cb..0b02d00f 100644 --- a/jedi/inference/syntax_tree.py +++ b/jedi/inference/syntax_tree.py @@ -753,6 +753,8 @@ def tree_name_to_values(inference_state, context, tree_name): types = NO_VALUES elif typ == 'del_stmt': types = NO_VALUES + elif typ == 'namedexpr_test': + types = infer_node(context, node) else: raise ValueError("Should not happen. type: %s" % typ) return types diff --git a/jedi/parser_utils.py b/jedi/parser_utils.py index 81c8391e..9c1290be 100644 --- a/jedi/parser_utils.py +++ b/jedi/parser_utils.py @@ -90,7 +90,7 @@ def get_flow_branch_keyword(flow_node, node): first_leaf = child.get_first_leaf() if first_leaf in _FLOW_KEYWORDS: keyword = first_leaf - return 0 + return None def clean_scope_docstring(scope_node): @@ -239,7 +239,7 @@ def get_parent_scope(node, include_flows=False): return None # It's a module already. while True: - if is_scope(scope) or include_flows and isinstance(scope, tree.Flow): + if is_scope(scope): if scope.type in ('classdef', 'funcdef', 'lambdef'): index = scope.children.index(':') if scope.children[index].start_pos >= node.start_pos: @@ -251,6 +251,14 @@ def get_parent_scope(node, include_flows=False): scope = scope.parent continue return scope + elif include_flows and isinstance(scope, tree.Flow): + # The cursor might be on `if foo`, so the parent scope will not be + # the if, but the parent of the if. + if not (scope.type == 'if_stmt' + and any(n.start_pos <= node.start_pos < n.end_pos + for n in scope.get_test_nodes())): + return scope + scope = scope.parent diff --git a/test/completion/flow_analysis.py b/test/completion/flow_analysis.py index ed1f0533..4082fb0e 100644 --- a/test/completion/flow_analysis.py +++ b/test/completion/flow_analysis.py @@ -5,7 +5,7 @@ x = 3 if NOT_DEFINED: x = '' -#? 6 int() +#? 6 int() str() elif x: pass else: diff --git a/test/completion/named_expression.py b/test/completion/named_expression.py index 37c835a5..11293b68 100644 --- a/test/completion/named_expression.py +++ b/test/completion/named_expression.py @@ -1,3 +1,6 @@ +# For assignment expressions / named expressions / walrus operators / whatever +# they are called. + # python >= 3.8 b = (a:=1, a) @@ -11,3 +14,39 @@ b = ('':=1,) #? int() b[0] + +def test_assignments(): + match = '' + #? str() + match + #? 8 int() + if match := 1: + #? int() + match + #? int() + match + +def test_assignments2(): + class Foo: + match = '' + #? str() + Foo.match + #? 13 int() + if Foo.match := 1: + #? str() + Foo.match + #? str() + Foo.match + + #? + y + #? 16 str() + if y := Foo.match: + #? str() + y + #? str() + y + + #? 8 str() + if z := Foo.match: + pass From 58ef6cd36b2d7214f0dc70d8b8dfda80c241d295 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 5 Aug 2020 23:49:15 +0200 Subject: [PATCH 43/74] if_stmt test clauses should be resolved at the start of the if_stmt --- jedi/inference/syntax_tree.py | 5 ++++- test/completion/flow_analysis.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/jedi/inference/syntax_tree.py b/jedi/inference/syntax_tree.py index 0b02d00f..18694d87 100644 --- a/jedi/inference/syntax_tree.py +++ b/jedi/inference/syntax_tree.py @@ -288,10 +288,13 @@ def infer_atom(context, atom): if atom.type == 'name': # This is the first global lookup. stmt = tree.search_ancestor( - atom, 'expr_stmt', 'lambdef' + atom, 'expr_stmt', 'lambdef', 'if_stmt' ) or atom if stmt.type == 'lambdef': stmt = atom + if stmt.type == 'if_stmt': + if not any(n.start_pos <= atom.start_pos < n.end_pos for n in stmt.get_test_nodes()): + stmt = atom position = stmt.start_pos if _is_annotation_name(atom): # Since Python 3.7 (with from __future__ import annotations), diff --git a/test/completion/flow_analysis.py b/test/completion/flow_analysis.py index 4082fb0e..ed1f0533 100644 --- a/test/completion/flow_analysis.py +++ b/test/completion/flow_analysis.py @@ -5,7 +5,7 @@ x = 3 if NOT_DEFINED: x = '' -#? 6 int() str() +#? 6 int() elif x: pass else: From e617c9d3440a95148bd4667f7b51a92ddb8b3399 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 5 Aug 2020 23:52:38 +0200 Subject: [PATCH 44/74] Formatting --- jedi/inference/syntax_tree.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/jedi/inference/syntax_tree.py b/jedi/inference/syntax_tree.py index 18694d87..192328f3 100644 --- a/jedi/inference/syntax_tree.py +++ b/jedi/inference/syntax_tree.py @@ -287,14 +287,12 @@ def infer_atom(context, atom): state = context.inference_state if atom.type == 'name': # This is the first global lookup. - stmt = tree.search_ancestor( - atom, 'expr_stmt', 'lambdef', 'if_stmt' - ) or atom - if stmt.type == 'lambdef': - stmt = atom + stmt = tree.search_ancestor(atom, 'expr_stmt', 'lambdef', 'if_stmt') or atom if stmt.type == 'if_stmt': if not any(n.start_pos <= atom.start_pos < n.end_pos for n in stmt.get_test_nodes()): stmt = atom + elif stmt.type == 'lambdef': + stmt = atom position = stmt.start_pos if _is_annotation_name(atom): # Since Python 3.7 (with from __future__ import annotations), From 216f976fd5cab7a460e5d287e853d11759251e52 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Thu, 6 Aug 2020 00:12:50 +0200 Subject: [PATCH 45/74] Add a .readthedocs.yml to make sure that it's properly pip installed before the documentation is built --- .readthedocs.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..1893a4fd --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,2 @@ +python: + pip_install: true From ff439039da7c7fa966706cee629ba81c4e65c003 Mon Sep 17 00:00:00 2001 From: Mvdk <40647401+mvanderkamp@users.noreply.github.com> Date: Mon, 14 Sep 2020 10:27:19 -0600 Subject: [PATCH 46/74] make contextualized_node an optional kwarg In all other py__iter__ definitions that I found, this argument is optional. It also often seems to not be passed around. I'm not sure why it was deemed mandatory here despite not being used. --- jedi/plugins/stdlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index d9733dff..fddde91f 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -257,7 +257,7 @@ class ReversedObject(AttributeOverwrite): super().__init__(reversed_obj) self._iter_list = iter_list - def py__iter__(self, contextualized_node): + def py__iter__(self, contextualized_node=None): return self._iter_list @publish_method('__next__') From 5b81abd53739fff9a1faec37d14135d7e4f632ac Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Thu, 6 Aug 2020 00:53:03 +0200 Subject: [PATCH 47/74] Mention different language servers in README --- README.rst | 6 ++++++ docs/docs/usage.rst | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 80590f76..959a5f68 100644 --- a/README.rst +++ b/README.rst @@ -55,6 +55,12 @@ Jedi can currently be used with the following editors/projects: and many more! +There are a few language servers that use Jedi: + +- `jedi-language-server `_ +- `python-language-server `_ +- `anakin-language-server `_ + Here are some pictures taken from jedi-vim_: .. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png diff --git a/docs/docs/usage.rst b/docs/docs/usage.rst index b148ad76..84b0078e 100644 --- a/docs/docs/usage.rst +++ b/docs/docs/usage.rst @@ -3,11 +3,21 @@ Using Jedi ========== -|jedi| is can be used with a variety of plugins and software. It is also possible -to use |jedi| in the :ref:`Python shell or with IPython `. +|jedi| is can be used with a variety of :ref:`plugins `, +`language servers ` and other software. +It is also possible to use |jedi| in the :ref:`Python shell or with IPython +`. Below you can also find a list of :ref:`recipes for type hinting `. +.. _language-servers: + +Language Servers +-------------- + +- `jedi-language-server `_ +- `python-language-server `_ +- `anakin-language-server `_ .. _editor-plugins: From fa2abb5ff6a67d8f9cddd9ea915d5a6170b8aa25 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 19 Sep 2020 20:36:54 +0200 Subject: [PATCH 48/74] Add mypy cache to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1b4a42b9..bdb15c94 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ jedi.egg-info/ record.json /.cache/ /.pytest_cache +/.mypy_cache /venv/ From f18493b627b55071831d6714f229724f3ece5aec Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 19 Sep 2020 20:57:32 +0200 Subject: [PATCH 49/74] Fix an interpreter test --- test/test_api/test_interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 33d33816..6f790bef 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -621,7 +621,7 @@ def bar(): # typing is available via globals. ({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''), - ({'return': 'typing.Union["str", int]'}, ['int'], ''), + ({'return': 'typing.Union["str", int]'}, ['int', 'str'], ''), ({'return': 'typing.Union["str", 1]'}, [], ''), ({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''), ({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg From 39fe9a19799ca163d38fe9136b399581e350741a Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 19 Sep 2020 20:58:03 +0200 Subject: [PATCH 50/74] Add the Python 3.9 environment --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1588ee1b..8c78f407 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ python: - 3.6 env: + - JEDI_TEST_ENVIRONMENT=39 - JEDI_TEST_ENVIRONMENT=38 - JEDI_TEST_ENVIRONMENT=37 - JEDI_TEST_ENVIRONMENT=36 From 66e2a0fce41c62309c6b08ca007342b81f767072 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 19 Sep 2020 21:15:03 +0200 Subject: [PATCH 51/74] implict_reexport needs to be True for parso --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 904b5ffe..98a775aa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,6 +49,6 @@ implicit_reexport = False strict_equality = True -[mypy-jedi,jedi.inference.compiled,jedi.inference.value] +[mypy-jedi,jedi.inference.compiled,jedi.inference.value,parso] # Various __init__.py files which contain re-exports we want to implicitly make. implicit_reexport = True From 4082728c32e3d4a36357de7cc76b84dcc9c57ab6 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 19 Sep 2020 21:22:38 +0200 Subject: [PATCH 52/74] Revert "Add the Python 3.9 environment" This reverts commit 39fe9a19799ca163d38fe9136b399581e350741a. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8c78f407..1588ee1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ python: - 3.6 env: - - JEDI_TEST_ENVIRONMENT=39 - JEDI_TEST_ENVIRONMENT=38 - JEDI_TEST_ENVIRONMENT=37 - JEDI_TEST_ENVIRONMENT=36 From c1f4e7d8748d0f1efe30332e1d41089560e1c319 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 19 Sep 2020 21:27:55 +0200 Subject: [PATCH 53/74] One interpreter test is different for 3.9+ --- test/test_api/test_interpreter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 6f790bef..4615764e 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -621,7 +621,8 @@ def bar(): # typing is available via globals. ({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''), - ({'return': 'typing.Union["str", int]'}, ['int', 'str'], ''), + ({'return': 'typing.Union["str", int]'}, + ['int', 'str'] if sys.version_info >= (3, 9) else ['int'], ''), ({'return': 'typing.Union["str", 1]'}, [], ''), ({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''), ({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg From e3fedb52f16c328e3d45e466a9c7a4e85c924bfc Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 19 Sep 2020 21:40:01 +0200 Subject: [PATCH 54/74] Remove an unused import --- jedi/inference/compiled/mixed.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jedi/inference/compiled/mixed.py b/jedi/inference/compiled/mixed.py index 86675794..ef82a574 100644 --- a/jedi/inference/compiled/mixed.py +++ b/jedi/inference/compiled/mixed.py @@ -3,7 +3,6 @@ Used only for REPL Completion. """ import inspect -import os from pathlib import Path from jedi.parser_utils import get_cached_code_lines From cb55b45d47ba29dce71798bb9d236e1c1a47bb59 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 19 Sep 2020 22:13:45 +0200 Subject: [PATCH 55/74] Catch an OSError on Windows --- jedi/inference/compiled/mixed.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/jedi/inference/compiled/mixed.py b/jedi/inference/compiled/mixed.py index ef82a574..43e0ed6c 100644 --- a/jedi/inference/compiled/mixed.py +++ b/jedi/inference/compiled/mixed.py @@ -191,8 +191,15 @@ def _find_syntax_node_name(inference_state, python_object): # The type might not be known (e.g. class_with_dict.__weakref__) return None path = None if path is None else Path(path) - if path is None or not path.exists(): - # The path might not exist or be e.g. . + try: + if path is None or not path.exists(): + # The path might not exist or be e.g. . + return None + except OSError: + # Might raise an OSError on Windows: + # + # [WinError 123] The filename, directory name, or volume label + # syntax is incorrect: '' return None file_io = FileIO(path) From 04572422d4e4e650de85f54fd7dc7c4e50d7fbce Mon Sep 17 00:00:00 2001 From: anki-code Date: Tue, 29 Sep 2020 01:12:48 +0300 Subject: [PATCH 56/74] Xonsh shell has jedi extension (#1674) * xonsh shell has jedy extension * jedin in xonsh shell usage * and many more! :) --- README.rst | 1 + docs/docs/usage.rst | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/README.rst b/README.rst index 959a5f68..f63feb8d 100644 --- a/README.rst +++ b/README.rst @@ -52,6 +52,7 @@ Jedi can currently be used with the following editors/projects: - wdb_ - Web Debugger - `Eric IDE`_ (Available as a plugin) - `IPython 6.0.0+ `_ +- `xonsh shell `_ has `jedi extension `_ and many more! diff --git a/docs/docs/usage.rst b/docs/docs/usage.rst index 84b0078e..2ef8b848 100644 --- a/docs/docs/usage.rst +++ b/docs/docs/usage.rst @@ -93,6 +93,16 @@ Web Debugger - wdb_ +xonsh shell +~~~~~~~~~~~ + +Jedi is a preinstalled extension in `xonsh shell `_. +Run the following command to enable: + +:: + + xontrib load jedi + and many more! .. _repl-completion: From a5a36a049c77a50f78ba6a0d139378d43b1a67ab Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 19:08:41 +0200 Subject: [PATCH 57/74] Fix an infer issue on literals after brackets, fixes #1657 --- jedi/api/__init__.py | 5 +++++ test/test_api/test_api.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index a0430e90..a47e66db 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -281,6 +281,11 @@ class Script: leaf = self._module_node.get_leaf_for_position(pos) if leaf is None or leaf.type == 'string': return [] + if leaf.end_pos == (line, column) and leaf.type == 'operator': + next_ = leaf.get_next_leaf() + if next_.start_pos == leaf.end_pos \ + and next_.type in ('number', 'string', 'keyword'): + leaf = next_ context = self._get_module_context().create_context(leaf) diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index 1829e30f..51c0da3b 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -370,3 +370,35 @@ def test_multi_goto(Script): y, = script.goto(line=4) assert x.line == 1 assert y.line == 2 + + +@pytest.mark.parametrize( + 'code, column, expected', [ + ('str() ', 3, 'str'), + ('str() ', 4, 'str'), + ('str() ', 5, 'str'), + ('str() ', 6, None), + ('str( ) ', 6, None), + (' 1', 1, None), + ('str(1) ', 3, 'str'), + ('str(1) ', 4, 'int'), + ('str(1) ', 5, 'int'), + ('str(1) ', 6, 'str'), + ('str(1) ', 7, None), + ('str( 1) ', 4, 'str'), + ('str( 1) ', 5, 'int'), + ('str(+1) ', 4, 'str'), + ('str(+1) ', 5, 'int'), + ('str(1, 1.) ', 3, 'str'), + ('str(1, 1.) ', 4, 'int'), + ('str(1, 1.) ', 5, 'int'), + ('str(1, 1.) ', 6, None), + ('str(1, 1.) ', 7, 'float'), + ] +) +def test_infer_after_parentheses(Script, code, column, expected): + completions = Script(code).infer(column=column) + if expected is None: + assert completions == [] + else: + assert [c.name for c in completions] == [expected] From e671a0cb6dd994ac1939681f297179bf6f801a85 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 20:25:00 +0200 Subject: [PATCH 58/74] Fix an error with enums, fixes #1675 --- jedi/plugins/stdlib.py | 11 ++++++++++- test/test_inference/test_signature.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index fddde91f..17f1df3b 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -819,7 +819,8 @@ def get_metaclass_filters(func): and metaclass.get_root_context().py__name__() == 'enum': filter_ = ParserTreeFilter(parent_context=cls.as_context()) return [DictFilter({ - name.string_name: EnumInstance(cls, name).name for name in filter_.values() + name.string_name: EnumInstance(cls, name).name + for name in filter_.values() })] return func(cls, metaclasses, is_instance) return wrapper @@ -837,6 +838,14 @@ class EnumInstance(LazyValueWrapper): return ValueName(self, self._name.tree_name) def _get_wrapped_value(self): + n = self._name.string_name + if n.startswith('__') and n.endswith('__') or self._name.api_type == 'function': + inferred = self._name.infer() + if inferred: + return next(iter(inferred)) + o, = self.inference_state.builtins_module.py__getattribute__('object') + return o + value, = self._cls.execute_with_values() return value diff --git a/test/test_inference/test_signature.py b/test/test_inference/test_signature.py index 500759bc..02f119a8 100644 --- a/test/test_inference/test_signature.py +++ b/test/test_inference/test_signature.py @@ -340,3 +340,20 @@ def test_overload(Script, code): x1, x2 = Script(code, path=os.path.join(dir_, 'foo.py')).get_signatures() assert x1.to_string() == 'with_overload(x: int, y: int) -> float' assert x2.to_string() == 'with_overload(x: str, y: list) -> float' + + +def test_enum(Script): + script = Script('''\ + from enum import Enum + + class Planet(Enum): + MERCURY = (3.303e+23, 2.4397e6) + VENUS = (4.869e+24, 6.0518e6) + + def __init__(self, mass, radius): + self.mass = mass # in kilograms + self.radius = radius # in meters + + Planet.MERCURY''') + completion, = script.complete() + assert not completion.get_signatures() From bf310c780c7d9f58f4e4f2e6688ccbdfd0b9d834 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 21:04:36 +0200 Subject: [PATCH 59/74] Fix a recursion on imports, fixes #1677 --- jedi/inference/names.py | 7 +++++++ test/examples/import-recursion/cadquery_simple/__init__.py | 1 + test/examples/import-recursion/cadquery_simple/cq.py | 1 + test/examples/import-recursion/cq_example.py | 3 +++ test/test_inference/test_imports.py | 6 ++++++ 5 files changed, 18 insertions(+) create mode 100644 test/examples/import-recursion/cadquery_simple/__init__.py create mode 100644 test/examples/import-recursion/cadquery_simple/cq.py create mode 100644 test/examples/import-recursion/cq_example.py diff --git a/jedi/inference/names.py b/jedi/inference/names.py index 38be6f22..2be1427c 100644 --- a/jedi/inference/names.py +++ b/jedi/inference/names.py @@ -7,6 +7,7 @@ from parso.tree import search_ancestor from jedi.parser_utils import find_statement_documentation, clean_scope_docstring from jedi.inference.utils import unite from jedi.inference.base_value import ValueSet, NO_VALUES +from jedi.inference.cache import inference_state_method_cache from jedi.inference import docstrings from jedi.cache import memoize_method from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf @@ -331,6 +332,12 @@ class TreeNameDefinition(AbstractTreeName): node = node.parent return indexes + @property + def inference_state(self): + # Used by the cache function below + return self.parent_context.inference_state + + @inference_state_method_cache(default='') def py__doc__(self): api_type = self.api_type if api_type in ('function', 'class'): diff --git a/test/examples/import-recursion/cadquery_simple/__init__.py b/test/examples/import-recursion/cadquery_simple/__init__.py new file mode 100644 index 00000000..e87c6632 --- /dev/null +++ b/test/examples/import-recursion/cadquery_simple/__init__.py @@ -0,0 +1 @@ +from .cq import selectors diff --git a/test/examples/import-recursion/cadquery_simple/cq.py b/test/examples/import-recursion/cadquery_simple/cq.py new file mode 100644 index 00000000..90bb9ac7 --- /dev/null +++ b/test/examples/import-recursion/cadquery_simple/cq.py @@ -0,0 +1 @@ +from . import selectors diff --git a/test/examples/import-recursion/cq_example.py b/test/examples/import-recursion/cq_example.py new file mode 100644 index 00000000..00a57176 --- /dev/null +++ b/test/examples/import-recursion/cq_example.py @@ -0,0 +1,3 @@ +import cadquery_simple as cq + +cq. diff --git a/test/test_inference/test_imports.py b/test/test_inference/test_imports.py index f1eb3f57..8e49244f 100644 --- a/test/test_inference/test_imports.py +++ b/test/test_inference/test_imports.py @@ -468,3 +468,9 @@ def test_relative_import_star(Script): script = Script(source, path='export.py') assert script.complete(3, len("furl.c")) + + +def test_import_recursion(Script): + path = get_example_dir('import-recursion', "cq_example.py") + for c in Script(path=path).complete(3, 3): + c.docstring() From 49e35497ae6246cd26592e14628d06f1cdd59389 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 21:28:08 +0200 Subject: [PATCH 60/74] Stop subclassing CompiledName, potentially fixes #1667 --- jedi/inference/value/instance.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/jedi/inference/value/instance.py b/jedi/inference/value/instance.py index 9514e01d..ac0574c7 100644 --- a/jedi/inference/value/instance.py +++ b/jedi/inference/value/instance.py @@ -401,21 +401,10 @@ class AnonymousInstance(_BaseTreeInstance): _arguments = None -class CompiledInstanceName(compiled.CompiledName): - def __init__(self, inference_state, instance, klass, name): - parent_value = klass.parent_context.get_value() - assert parent_value is not None, "How? Please reproduce and report" - super().__init__( - inference_state, - parent_value, - name.string_name - ) - self._instance = instance - self._class_member_name = name - +class CompiledInstanceName(NameWrapper): @iterator_to_value_set def infer(self): - for result_value in self._class_member_name.infer(): + for result_value in self._wrapped_name.infer(): if result_value.api_type == 'function': yield CompiledBoundMethod(result_value) else: @@ -434,11 +423,7 @@ class CompiledInstanceClassFilter(AbstractFilter): return self._convert(self._class_filter.values()) def _convert(self, names): - klass = self._class_filter.compiled_value - return [ - CompiledInstanceName(self._instance.inference_state, self._instance, klass, n) - for n in names - ] + return [CompiledInstanceName(n) for n in names] class BoundMethod(FunctionMixin, ValueWrapper): From a4f45993f80df69cc9e5dcb37e113d3f1d441828 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 21:38:39 +0200 Subject: [PATCH 61/74] Simplify some things, so something like #1678 does not happen again --- jedi/inference/compiled/value.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/jedi/inference/compiled/value.py b/jedi/inference/compiled/value.py index 1b34b123..88732f01 100644 --- a/jedi/inference/compiled/value.py +++ b/jedi/inference/compiled/value.py @@ -324,8 +324,7 @@ class CompiledName(AbstractNameDefinition): self.string_name = name def py__doc__(self): - value, = self.infer() - return value.py__doc__() + return self.infer_compiled_value().py__doc__() def _get_qualified_names(self): parent_qualified_names = self.parent_context.get_qualified_names() @@ -349,16 +348,12 @@ class CompiledName(AbstractNameDefinition): @property def api_type(self): - api = self.infer() - # If we can't find the type, assume it is an instance variable - if not api: - return "instance" - return next(iter(api)).api_type + return self.infer_compiled_value().api_type - @memoize_method def infer(self): return ValueSet([self.infer_compiled_value()]) + @memoize_method def infer_compiled_value(self): return create_from_name(self._inference_state, self._parent_value, self.string_name) From 6eabde1519c07a102ff8d96f117e7c5086c5f9dd Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 23:26:07 +0200 Subject: [PATCH 62/74] Fix annotations on self attributes, fixes #1681 --- jedi/inference/value/instance.py | 12 ++++++++++++ test/completion/pep0484_basic.py | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/jedi/inference/value/instance.py b/jedi/inference/value/instance.py index ac0574c7..c2c20359 100644 --- a/jedi/inference/value/instance.py +++ b/jedi/inference/value/instance.py @@ -501,6 +501,18 @@ class SelfName(TreeNameDefinition): def get_defining_qualified_value(self): return self._instance + def infer(self): + stmt = search_ancestor(self.tree_name, 'expr_stmt') + if stmt is not None: + if stmt.children[1].type == "annassign": + from jedi.inference.gradual.annotation import infer_annotation + values = infer_annotation( + self.parent_context, stmt.children[1].children[1] + ).execute_annotation() + if values: + return values + return super().infer() + class LazyInstanceClassName(NameWrapper): def __init__(self, instance, class_member_name): diff --git a/test/completion/pep0484_basic.py b/test/completion/pep0484_basic.py index 54ec8770..19c4cfa4 100644 --- a/test/completion/pep0484_basic.py +++ b/test/completion/pep0484_basic.py @@ -179,3 +179,13 @@ def argskwargs(*args: int, **kwargs: float): next(iter(kwargs.keys())) #? float() kwargs[''] + + +class NotCalledClass: + def __init__(self, x): + self.x: int = x + self.y: int = '' + #? int() + self.x + #? int() + self.y From 98d0a55a02194522f7f0951b3cfdbea7d9b03032 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 23:32:28 +0200 Subject: [PATCH 63/74] Add a few more tests for annotations on self --- test/completion/pep0484_basic.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/completion/pep0484_basic.py b/test/completion/pep0484_basic.py index 19c4cfa4..e20fe440 100644 --- a/test/completion/pep0484_basic.py +++ b/test/completion/pep0484_basic.py @@ -189,3 +189,12 @@ class NotCalledClass: self.x #? int() self.y + #? int() + self.y + self.z: int + self.z = '' + #? str() int() + self.z + self.w: float + #? float() + self.w From 6094e7b39a6382edac8de940816414502ed1f5fe Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 24 Oct 2020 10:12:21 +0200 Subject: [PATCH 64/74] Fix get_line_code for stubs --- jedi/api/__init__.py | 2 +- test/test_inference/test_gradual/test_conversion.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index a47e66db..a1c3c5a6 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -160,7 +160,7 @@ class Script: self._module_node, code = self._inference_state.parse_and_get_code( code=code, path=self.path, - use_latest_grammar=path and path.suffix == 'pyi', + use_latest_grammar=path and path.suffix == '.pyi', cache=False, # No disk cache, because the current script often changes. diff_cache=settings.fast_parser, cache_path=settings.cache_directory, diff --git a/test/test_inference/test_gradual/test_conversion.py b/test/test_inference/test_gradual/test_conversion.py index fd201aee..809f87ae 100644 --- a/test/test_inference/test_gradual/test_conversion.py +++ b/test/test_inference/test_gradual/test_conversion.py @@ -1,4 +1,5 @@ import os +from parso.cache import parser_cache from test.helpers import root_dir from jedi.api.project import Project @@ -64,6 +65,17 @@ def test_goto_import(Script): assert not d.is_stub() +def test_stub_get_line_code(Script): + code = 'from abc import ABC; ABC' + script = Script(code) + d, = script.goto(only_stubs=True) + assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' + del parser_cache[script._inference_state.latest_grammar._hashed][str(d.module_path)] + d, = Script(path=d.module_path).goto(d.line, d.column, only_stubs=True) + assert d.is_stub() + assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' + + def test_os_stat_result(Script): d, = Script('import os; os.stat_result').goto() assert d.is_stub() From a03a093e2ccff736d3c856bd4c25bd8366f1b1d5 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 24 Oct 2020 10:19:20 +0200 Subject: [PATCH 65/74] change the create_stub_module stuff a bit --- jedi/api/__init__.py | 1 + jedi/inference/gradual/typeshed.py | 9 +++++---- jedi/inference/gradual/utils.py | 5 +++-- jedi/inference/imports.py | 4 ++-- test/test_inference/test_gradual/test_conversion.py | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index a1c3c5a6..34718881 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -196,6 +196,7 @@ class Script: # We are in a stub file. Try to load the stub properly. stub_module = load_proper_stub_module( self._inference_state, + self._inference_state.latest_grammar, file_io, names, self._module_node diff --git a/jedi/inference/gradual/typeshed.py b/jedi/inference/gradual/typeshed.py index 6295294d..a4b28786 100644 --- a/jedi/inference/gradual/typeshed.py +++ b/jedi/inference/gradual/typeshed.py @@ -279,8 +279,8 @@ def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, impo return None else: return create_stub_module( - inference_state, python_value_set, stub_module_node, file_io, - import_names + inference_state, inference_state.latest_grammar, python_value_set, + stub_module_node, file_io, import_names ) @@ -294,7 +294,8 @@ def parse_stub_module(inference_state, file_io): ) -def create_stub_module(inference_state, python_value_set, stub_module_node, file_io, import_names): +def create_stub_module(inference_state, grammar, python_value_set, + stub_module_node, file_io, import_names): if import_names == ('typing',): module_cls = TypingModuleWrapper else: @@ -306,7 +307,7 @@ def create_stub_module(inference_state, python_value_set, stub_module_node, file string_names=import_names, # The code was loaded with latest_grammar, so use # that. - code_lines=get_cached_code_lines(inference_state.latest_grammar, file_io.path), + code_lines=get_cached_code_lines(grammar, file_io.path), is_package=file_name == '__init__.pyi', ) return stub_module_value diff --git a/jedi/inference/gradual/utils.py b/jedi/inference/gradual/utils.py index 1fc90e7f..af3703c7 100644 --- a/jedi/inference/gradual/utils.py +++ b/jedi/inference/gradual/utils.py @@ -3,7 +3,7 @@ from pathlib import Path from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module -def load_proper_stub_module(inference_state, file_io, import_names, module_node): +def load_proper_stub_module(inference_state, grammar, file_io, import_names, module_node): """ This function is given a random .pyi file and should return the proper module. @@ -27,7 +27,8 @@ def load_proper_stub_module(inference_state, file_io, import_names, module_node) actual_value_set = inference_state.import_module(import_names, prefer_stubs=False) stub = create_stub_module( - inference_state, actual_value_set, module_node, file_io, import_names + inference_state, grammar, actual_value_set, + module_node, file_io, import_names ) inference_state.stub_module_cache[import_names] = stub return stub diff --git a/jedi/inference/imports.py b/jedi/inference/imports.py index 5ae5819f..f464b08b 100644 --- a/jedi/inference/imports.py +++ b/jedi/inference/imports.py @@ -498,8 +498,8 @@ def load_module_from_path(inference_state, file_io, import_names=None, is_packag values = NO_VALUES return create_stub_module( - inference_state, values, parse_stub_module(inference_state, file_io), - file_io, import_names + inference_state, inference_state.latest_grammar, values, + parse_stub_module(inference_state, file_io), file_io, import_names ) else: module = _load_python_module( diff --git a/test/test_inference/test_gradual/test_conversion.py b/test/test_inference/test_gradual/test_conversion.py index 809f87ae..c77a06b0 100644 --- a/test/test_inference/test_gradual/test_conversion.py +++ b/test/test_inference/test_gradual/test_conversion.py @@ -70,7 +70,7 @@ def test_stub_get_line_code(Script): script = Script(code) d, = script.goto(only_stubs=True) assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' - del parser_cache[script._inference_state.latest_grammar._hashed][str(d.module_path)] + del parser_cache[script._inference_state.latest_grammar._hashed][d.module_path] d, = Script(path=d.module_path).goto(d.line, d.column, only_stubs=True) assert d.is_stub() assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' From 69750b9bf0c41c3a202a77621c14994f1f2dbcba Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 24 Oct 2020 13:40:19 +0200 Subject: [PATCH 66/74] Add Python 3.9 to the testsed environments --- .travis.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1588ee1b..e16e2689 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ python: env: - JEDI_TEST_ENVIRONMENT=38 + - JEDI_TEST_ENVIRONMENT=39 - JEDI_TEST_ENVIRONMENT=37 - JEDI_TEST_ENVIRONMENT=36 - JEDI_TEST_ENVIRONMENT=interpreter @@ -47,9 +48,15 @@ script: # Only required for JEDI_TEST_ENVIRONMENT=38, because it's not always # available. download_name=python-$test_env_version - wget https://s3.amazonaws.com/travis-python-archives/binaries/ubuntu/16.04/x86_64/$download_name.tar.bz2 - sudo tar xjf $download_name.tar.bz2 --directory / opt/python - ln -s "/opt/python/${test_env_version}/bin/python" /home/travis/bin/$python_bin + if [ "$JEDI_TEST_ENVIRONMENT" == "39" ]; then + wget https://storage.googleapis.com/travis-ci-language-archives/python/binaries/ubuntu/16.04/x86_64/python-3.9-dev.tar.bz2 + sudo tar xjf python-3.9-dev.tar.bz2 --directory / opt/python + ln -s "/opt/python/3.9-dev/bin/python" /home/travis/bin/python3.9 + else + wget https://s3.amazonaws.com/travis-python-archives/binaries/ubuntu/16.04/x86_64/$download_name.tar.bz2 + sudo tar xjf $download_name.tar.bz2 --directory / opt/python + ln -s "/opt/python/${test_env_version}/bin/python" /home/travis/bin/$python_bin + fi elif [ "${python_path#/opt/pyenv/shims}" != "$python_path" ]; then # Activate pyenv version (required with JEDI_TEST_ENVIRONMENT=36). pyenv_bin="$(pyenv whence --path "$python_bin" | head -n1)" From 83d4ec9e847920e4b6a199a934468f467f9b9e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Labb=C3=A9?= Date: Sat, 5 Dec 2020 21:00:28 -0300 Subject: [PATCH 67/74] Catch 'PermissionError' for unreadable directories --- jedi/api/project.py | 7 +++++-- jedi/inference/sys_path.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/jedi/api/project.py b/jedi/api/project.py index 41554314..4ba6b7d0 100644 --- a/jedi/api/project.py +++ b/jedi/api/project.py @@ -366,8 +366,11 @@ class Project: def _is_potential_project(path): for name in _CONTAINS_POTENTIAL_PROJECT: - if path.joinpath(name).exists(): - return True + try: + if path.joinpath(name).exists(): + return True + except PermissionError: + continue return False diff --git a/jedi/inference/sys_path.py b/jedi/inference/sys_path.py index 48b9ac89..9dd588db 100644 --- a/jedi/inference/sys_path.py +++ b/jedi/inference/sys_path.py @@ -171,8 +171,11 @@ def _get_paths_from_buildout_script(inference_state, buildout_script_path): def _get_parent_dir_with_file(path: Path, filename): for parent in path.parents: - if parent.joinpath(filename).is_file(): - return parent + try: + if parent.joinpath(filename).is_file(): + return parent + except PermissionError: + continue return None From ccdf7eddf461db1d78b34391d92e0f3c234d645e Mon Sep 17 00:00:00 2001 From: Yoni Weill Date: Sat, 5 Dec 2020 21:11:32 +0200 Subject: [PATCH 68/74] add Completion.get_completion_prefix_length fixes #1687 --- jedi/api/classes.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index a8d9fb19..42aac726 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -749,6 +749,24 @@ class Completion(BaseName): return super().type + def get_completion_prefix_length(self): + """ + Returns the length of the prefix being completed. + For example, completing ``isinstance``:: + + isinstan# <-- Cursor is here + + would return 8, because len('isinstan') == 8. + + Assuming the following function definition:: + + def foo(param=0): + pass + + completing ``foo(par`` would return 3. + """ + return self._like_name_length + def __repr__(self): return '<%s: %s>' % (type(self).__name__, self._name.get_public_name()) From 12a2d10595fe7b9f7b88c76b75c05fad75cb531b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Labb=C3=A9?= Date: Sun, 6 Dec 2020 15:25:46 -0300 Subject: [PATCH 69/74] Catch 'OSError' instead of just 'PermissionError' --- jedi/api/project.py | 2 +- jedi/inference/sys_path.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/api/project.py b/jedi/api/project.py index 4ba6b7d0..8c438f26 100644 --- a/jedi/api/project.py +++ b/jedi/api/project.py @@ -369,7 +369,7 @@ def _is_potential_project(path): try: if path.joinpath(name).exists(): return True - except PermissionError: + except OSError: continue return False diff --git a/jedi/inference/sys_path.py b/jedi/inference/sys_path.py index 9dd588db..e701686f 100644 --- a/jedi/inference/sys_path.py +++ b/jedi/inference/sys_path.py @@ -174,7 +174,7 @@ def _get_parent_dir_with_file(path: Path, filename): try: if parent.joinpath(filename).is_file(): return parent - except PermissionError: + except OSError: continue return None From 47e60107b29c8c16dfcfe1fd906518f868521e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Labb=C3=A9?= Date: Sun, 6 Dec 2020 15:26:20 -0300 Subject: [PATCH 70/74] Add tests for 'test_get_parent_dir_with_file' and 'test_is_potential_project' --- test/test_api/test_project.py | 19 +++++++++++++++++++ test/test_inference/test_sys_path.py | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/test/test_api/test_project.py b/test/test_api/test_project.py index c8533618..9ca4686c 100644 --- a/test/test_api/test_project.py +++ b/test/test_api/test_project.py @@ -6,6 +6,7 @@ import pytest from ..helpers import get_example_dir, set_cwd, root_dir, test_dir from jedi import Interpreter from jedi.api import Project, get_default_project +from jedi.api.project import _is_potential_project, _CONTAINS_POTENTIAL_PROJECT def test_django_default_project(Script): @@ -160,3 +161,21 @@ def test_complete_search(Script, string, completions, all_scopes): project = Project(test_dir) defs = project.complete_search(string, all_scopes=all_scopes) assert [d.complete for d in defs] == completions + + +@pytest.mark.parametrize( + 'path,expected', [ + (Path(__file__).parents[2], True), # The path of the project + (Path(__file__).parents[1], False), # The path of the tests, not a project + (Path.home(), None) + ] +) +def test_is_potential_project(path, expected): + + if expected is None: + try: + expected = _CONTAINS_POTENTIAL_PROJECT in os.listdir(path) + except OSError: + expected = False + + assert _is_potential_project(path) == expected diff --git a/test/test_inference/test_sys_path.py b/test/test_inference/test_sys_path.py index 2fa0e4df..d22cc2e0 100644 --- a/test/test_inference/test_sys_path.py +++ b/test/test_inference/test_sys_path.py @@ -108,3 +108,13 @@ def test_transform_path_to_dotted(sys_path_, module_path, expected, is_package): module_path = os.path.abspath(module_path) assert sys_path.transform_path_to_dotted(sys_path_, Path(module_path)) \ == (expected, is_package) + + +@pytest.mark.parametrize( + 'path,filename,expected', [ + (Path(__file__).parents[1], "setup.py", Path(__file__).parents[2]), + (Path(__file__).parents[2], os.path.basename(__file__), None) + ] +) +def test_get_parent_dir_with_file(path, filename, expected): + assert sys_path._get_parent_dir_with_file(path, filename) == expected From 10958200060a546d5ed8250809fee5ae7e94ae0f Mon Sep 17 00:00:00 2001 From: Yoni Weill Date: Sun, 6 Dec 2020 21:09:03 +0200 Subject: [PATCH 71/74] add tests for get_completion_prefix_length --- test/test_api/test_classes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_api/test_classes.py b/test/test_api/test_classes.py index 6add83f6..4a6f7323 100644 --- a/test/test_api/test_classes.py +++ b/test/test_api/test_classes.py @@ -513,10 +513,14 @@ def test_added_equals_to_params(Script): assert run('foo(bar').name_with_symbols == 'bar=' assert run('foo(bar').complete == '=' + assert run('foo(bar').get_completion_prefix_length() == 3 assert run('foo(bar, baz').complete == '=' + assert run('foo(bar, baz').get_completion_prefix_length() == 3 assert run(' bar').name_with_symbols == 'bar' assert run(' bar').complete == '' + assert run(' bar').get_completion_prefix_length() == 3 x = run('foo(bar=isins').name_with_symbols + assert run('foo(bar=isins').get_completion_prefix_length() == 5 assert x == 'isinstance' From 06d6776422c4c06f0c312cfef3f2dacedfbda808 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 6 Dec 2020 18:07:19 -0800 Subject: [PATCH 72/74] Add tests for #1702, for a rare numpydoc syntax. It looks like numpydoc, and things like masked array docstrings use a syntax that make jedi crash: fill_value : {var}, optional Value used internally for the masked values. If ``fill_value`` is not None, it supersedes ``endwith``. Here we add a test that we do not crash jedi. --- test/test_inference/test_docstring.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/test_inference/test_docstring.py b/test/test_inference/test_docstring.py index 3fc54eb1..62d36683 100644 --- a/test/test_inference/test_docstring.py +++ b/test/test_inference/test_docstring.py @@ -206,6 +206,24 @@ def test_numpydoc_parameters_set_of_values(): assert 'capitalize' in names assert 'numerator' in names +@pytest.mark.skipif(numpydoc_unavailable, + reason='numpydoc module is unavailable') +def test_numpydoc_parameters_set_single_value(): + """ + This is found in numpy masked-array I'm not too sure what this means but should not crash + """ + s = dedent(''' + def foobar(x, y): + """ + Parameters + ---------- + x : {var}, optional + """ + x.''') + names = [c.name for c in jedi.Script(s).complete()] + # just don't crash + assert names == [] + @pytest.mark.skipif(numpydoc_unavailable, reason='numpydoc module is unavailable') From 4740178bdf24ae111c50fb114e1d1d668b2c1a18 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 6 Dec 2020 18:03:20 -0800 Subject: [PATCH 73/74] Not all nodes have children, protect agaisnt it. --- jedi/inference/docstrings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/inference/docstrings.py b/jedi/inference/docstrings.py index ee7a8d89..80afbecf 100644 --- a/jedi/inference/docstrings.py +++ b/jedi/inference/docstrings.py @@ -113,7 +113,7 @@ def _expand_typestr(type_str): elif type_str.startswith('{'): node = parse(type_str, version='3.7').children[0] if node.type == 'atom': - for leaf in node.children[1].children: + for leaf in getattr(node.children[1], "children", []): if leaf.type == 'number': if '.' in leaf.value: yield 'float' From 6dcae857a7a1ad333508400bda6d3c218e70d5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Labb=C3=A9?= Date: Mon, 7 Dec 2020 14:50:04 -0300 Subject: [PATCH 74/74] Remove 'test_get_parent_dir_with_file' --- test/test_inference/test_sys_path.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/test_inference/test_sys_path.py b/test/test_inference/test_sys_path.py index d22cc2e0..2fa0e4df 100644 --- a/test/test_inference/test_sys_path.py +++ b/test/test_inference/test_sys_path.py @@ -108,13 +108,3 @@ def test_transform_path_to_dotted(sys_path_, module_path, expected, is_package): module_path = os.path.abspath(module_path) assert sys_path.transform_path_to_dotted(sys_path_, Path(module_path)) \ == (expected, is_package) - - -@pytest.mark.parametrize( - 'path,filename,expected', [ - (Path(__file__).parents[1], "setup.py", Path(__file__).parents[2]), - (Path(__file__).parents[2], os.path.basename(__file__), None) - ] -) -def test_get_parent_dir_with_file(path, filename, expected): - assert sys_path._get_parent_dir_with_file(path, filename) == expected