From c09e21ae4bf63baa5723f73260581a50f317cb46 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 21 Jul 2020 22:19:24 +0100 Subject: [PATCH 01/26] 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 02/26] 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 03/26] 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 04/26] 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 05/26] 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 06/26] 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 07/26] 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 08/26] 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 09/26] 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 10/26] 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 11/26] 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 12/26] 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 13/26] 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 14/26] 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 15/26] 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 16/26] 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 17/26] 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 18/26] 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 b3edda30c4277ee932cd7cd6b458dc7b9e143367 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 26 Jul 2020 12:09:04 +0100 Subject: [PATCH 19/26] 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 20/26] 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 21/26] 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 22/26] 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 23/26] 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 24/26] 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 25/26] 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 cce3ecb1e45fe0c9ab0ff4d1e1e08de501c6ec56 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 4 Aug 2020 21:49:42 +0100 Subject: [PATCH 26/26] 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