From e2ab4c060fcf1f0f8eb836e5699a98ae46a34af7 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 24 Dec 2018 17:40:47 +0100 Subject: [PATCH] Move all the gradual typing stuff into one folder --- jedi/api/classes.py | 2 +- jedi/evaluate/context/function.py | 2 +- jedi/evaluate/context/iterable.py | 2 +- jedi/evaluate/context/klass.py | 4 +- jedi/evaluate/context/module.py | 5 +- .../{plugins => evaluate/gradual}/typeshed.py | 174 +++++++++--------- jedi/evaluate/{context => gradual}/typing.py | 5 +- jedi/evaluate/imports.py | 2 + jedi/evaluate/pep0484.py | 2 +- jedi/evaluate/syntax_tree.py | 2 +- jedi/plugins/__init__.py | 2 - test/test_api/test_api.py | 2 +- test/test_plugin/test_stub.py | 2 +- test/test_settings.py | 2 +- 14 files changed, 107 insertions(+), 101 deletions(-) rename jedi/{plugins => evaluate/gradual}/typeshed.py (80%) rename jedi/evaluate/{context => gradual}/typing.py (99%) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 1bfada41..1551a049 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -14,7 +14,7 @@ from jedi.evaluate import imports from jedi.evaluate import compiled from jedi.evaluate.imports import ImportName from jedi.evaluate.context import FunctionExecutionContext -from jedi.plugins.typeshed import StubOnlyModuleContext +from jedi.evaluate.gradual.typeshed import StubOnlyModuleContext from jedi.api.keywords import KeywordName diff --git a/jedi/evaluate/context/function.py b/jedi/evaluate/context/function.py index 21b74c70..5f0fc217 100644 --- a/jedi/evaluate/context/function.py +++ b/jedi/evaluate/context/function.py @@ -312,7 +312,7 @@ class FunctionExecutionContext(TreeContext): evaluator = self.evaluator is_coroutine = self.tree_node.parent.type == 'async_stmt' is_generator = bool(get_yield_exprs(evaluator, self.tree_node)) - from jedi.evaluate.context.typing import AnnotatedSubClass + from jedi.evaluate.gradual.typing import AnnotatedSubClass if is_coroutine: if is_generator: diff --git a/jedi/evaluate/context/iterable.py b/jedi/evaluate/context/iterable.py index af170367..81dac51d 100644 --- a/jedi/evaluate/context/iterable.py +++ b/jedi/evaluate/context/iterable.py @@ -208,7 +208,7 @@ class Sequence(BuiltinOverwrite, IterableMixin): @memoize_method def get_object(self): - from jedi.evaluate.context.typing import AnnotatedSubClass + from jedi.evaluate.gradual.typing import AnnotatedSubClass klass = compiled.builtin_from_name(self.evaluator, self.array_type) # TODO is this execute annotation wrong? it returns a context set?! return AnnotatedSubClass(klass, self._get_generics()).execute_annotation() diff --git a/jedi/evaluate/context/klass.py b/jedi/evaluate/context/klass.py index afa93822..5253b5f9 100644 --- a/jedi/evaluate/context/klass.py +++ b/jedi/evaluate/context/klass.py @@ -251,7 +251,7 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, TreeContext)): )] def py__getitem__(self, index_context_set, contextualized_node): - from jedi.evaluate.context.typing import AnnotatedClass + from jedi.evaluate.gradual.typing import AnnotatedClass if not index_context_set: return ContextSet([self]) return ContextSet( @@ -264,7 +264,7 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, TreeContext)): ) def define_generics(self, type_var_dict): - from jedi.evaluate.context.typing import AnnotatedSubClass + from jedi.evaluate.gradual.typing import AnnotatedSubClass def remap_type_vars(): for type_var in self.list_type_vars(): diff --git a/jedi/evaluate/context/module.py b/jedi/evaluate/context/module.py index 7dd85f16..c5ec7383 100644 --- a/jedi/evaluate/context/module.py +++ b/jedi/evaluate/context/module.py @@ -9,7 +9,6 @@ from jedi.evaluate.filters import GlobalNameFilter, ContextNameMixin, \ AbstractNameDefinition, ParserTreeFilter, DictFilter, MergedFilter from jedi.evaluate import compiled from jedi.evaluate.base_context import TreeContext -from jedi.evaluate.imports import SubModuleName, infer_import class _ModuleAttributeName(AbstractNameDefinition): @@ -84,6 +83,8 @@ class ModuleMixin(object): Lists modules in the directory of this module (if this module is a package). """ + from jedi.evaluate.imports import SubModuleName + names = {} try: method = self.py__path__ @@ -120,6 +121,8 @@ class ModuleMixin(object): # to push the star imports into Evaluator.module_cache, if we reenable this. @evaluator_method_cache([]) def star_imports(self): + from jedi.evaluate.imports import infer_import + modules = [] for i in self.tree_node.iter_imports(): if i.is_star_import(): diff --git a/jedi/plugins/typeshed.py b/jedi/evaluate/gradual/typeshed.py similarity index 80% rename from jedi/plugins/typeshed.py rename to jedi/evaluate/gradual/typeshed.py index dc47152e..1b026cc1 100644 --- a/jedi/plugins/typeshed.py +++ b/jedi/evaluate/gradual/typeshed.py @@ -2,7 +2,6 @@ import os import re from jedi._compatibility import FileNotFoundError -from jedi.plugins.base import BasePlugin from jedi.evaluate.cache import evaluator_function_cache from jedi.cache import memoize_method from jedi.parser_utils import get_call_signature_for_any, get_cached_code_lines @@ -15,13 +14,12 @@ from jedi.evaluate.context import ModuleContext, FunctionContext, \ from jedi.evaluate.context.function import FunctionMixin from jedi.evaluate.context.klass import ClassMixin from jedi.evaluate.context.module import ModuleMixin -from jedi.evaluate.context.typing import TypingModuleFilterWrapper, \ +from jedi.evaluate.gradual.typing import TypingModuleFilterWrapper, \ TypingModuleName from jedi.evaluate.compiled.context import CompiledName from jedi.evaluate.utils import to_list, safe_property -from jedi.evaluate.imports import JediImportError -_jedi_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_jedi_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) _TYPESHED_PATH = os.path.join(_jedi_path, 'third_party', 'typeshed') @@ -95,95 +93,97 @@ def _merge_modules(context_set, stub_context): yield stub_context -class TypeshedPlugin(BasePlugin): - _version_cache = {} +_version_cache = {} - def _cache_stub_file_map(self, version_info): - """ - Returns a map of an importable name in Python to a stub file. - """ - # TODO this caches the stub files indefinitely, maybe use a time cache - # for that? - version = version_info[:2] + +def _cache_stub_file_map(version_info): + """ + Returns a map of an importable name in Python to a stub file. + """ + # TODO this caches the stub files indefinitely, maybe use a time cache + # for that? + version = version_info[:2] + try: + return _version_cache[version] + except KeyError: + pass + + _version_cache[version] = file_set = \ + _merge_create_stub_map(_get_typeshed_directories(version_info)) + return file_set + + +def import_module_decorator(func): + def wrapper(evaluator, import_names, parent_module_context, sys_path): + if import_names == ('_sqlite3',): + # TODO Maybe find a better solution for this? + # The problem is IMO how star imports are priorized and that + # there's no clear ordering. + return NO_CONTEXTS + + if import_names == ('os', 'path'): + # This is a huge exception, we follow a nested import + # ``os.path``, because it's a very important one in Python + # that is being achieved by messing with ``sys.modules`` in + # ``os``. + if parent_module_context is None: + parent_module_context, = evaluator.import_module(('os',)) + return parent_module_context.py__getattribute__('path') + + from jedi.evaluate.imports import JediImportError try: - return self._version_cache[version] - except KeyError: - pass + context_set = func( + evaluator, + import_names, + parent_module_context, + sys_path + ) + except JediImportError: + if import_names == ('typing',): + # TODO this is also quite ugly, please refactor. + context_set = NO_CONTEXTS + else: + raise - self._version_cache[version] = file_set = \ - _merge_create_stub_map(_get_typeshed_directories(version_info)) - return file_set + import_name = import_names[-1] + map_ = None + if len(import_names) == 1: + map_ = _cache_stub_file_map(evaluator.grammar.version_info) + elif isinstance(parent_module_context, StubModuleContext): + if not parent_module_context.stub_context.is_package(): + # Only if it's a package (= a folder) something can be + # imported. + return context_set + path = parent_module_context.stub_context.py__path__() + map_ = _merge_create_stub_map(path) - def import_module(self, callback): - def wrapper(evaluator, import_names, parent_module_context, sys_path): - if import_names == ('_sqlite3',): - # TODO Maybe find a better solution for this? - # The problem is IMO how star imports are priorized and that - # there's no clear ordering. - return NO_CONTEXTS - - if import_names == ('os', 'path'): - # This is a huge exception, we follow a nested import - # ``os.path``, because it's a very important one in Python - # that is being achieved by messing with ``sys.modules`` in - # ``os``. - if parent_module_context is None: - parent_module_context, = evaluator.import_module(('os',)) - return parent_module_context.py__getattribute__('path') - - try: - context_set = callback( - evaluator, - import_names, - parent_module_context, - sys_path - ) - except JediImportError: - if import_names == ('typing',): - # TODO this is also quite ugly, please refactor. - context_set = NO_CONTEXTS + if map_ is not None: + path = map_.get(import_name) + if path is not None: + try: + stub_module_node = _load_stub(evaluator, path) + except FileNotFoundError: + # The file has since been removed after looking for it. + # TODO maybe empty cache? + pass else: - raise - - import_name = import_names[-1] - map_ = None - if len(import_names) == 1: - map_ = self._cache_stub_file_map(evaluator.grammar.version_info) - elif isinstance(parent_module_context, StubModuleContext): - if not parent_module_context.stub_context.is_package(): - # Only if it's a package (= a folder) something can be - # imported. - return context_set - path = parent_module_context.stub_context.py__path__() - map_ = _merge_create_stub_map(path) - - if map_ is not None: - path = map_.get(import_name) - if path is not None: - try: - stub_module_node = _load_stub(evaluator, path) - except FileNotFoundError: - # The file has since been removed after looking for it. - # TODO maybe empty cache? - pass + if import_names == ('typing',): + module_cls = TypingModuleWrapper else: - if import_names == ('typing',): - module_cls = TypingModuleWrapper - else: - module_cls = StubOnlyModuleContext - stub_module_context = module_cls( - context_set, evaluator, stub_module_node, - path=path, - string_names=import_names, - # The code was loaded with latest_grammar, so use - # that. - code_lines=get_cached_code_lines(evaluator.latest_grammar, path), - ) - modules = _merge_modules(context_set, stub_module_context) - return ContextSet(modules) - # If no stub is found, just return the default. - return context_set - return wrapper + module_cls = StubOnlyModuleContext + stub_module_context = module_cls( + context_set, evaluator, stub_module_node, + path=path, + string_names=import_names, + # The code was loaded with latest_grammar, so use + # that. + code_lines=get_cached_code_lines(evaluator.latest_grammar, path), + ) + modules = _merge_modules(context_set, stub_module_context) + return ContextSet(modules) + # If no stub is found, just return the default. + return context_set + return wrapper class StubName(NameWrapper): diff --git a/jedi/evaluate/context/typing.py b/jedi/evaluate/gradual/typing.py similarity index 99% rename from jedi/evaluate/context/typing.py rename to jedi/evaluate/gradual/typing.py index 8a71ffcd..f77459ae 100644 --- a/jedi/evaluate/context/typing.py +++ b/jedi/evaluate/gradual/typing.py @@ -2,6 +2,8 @@ We need to somehow work with the typing objects. Since the typing objects are pretty bare we need to add all the Jedi customizations to make them work as contexts. + +This file deals with all the typing.py cases. """ from jedi._compatibility import unicode, force_unicode from jedi import debug @@ -16,7 +18,6 @@ from jedi.evaluate.utils import to_list from jedi.evaluate.filters import FilterWrapper, NameWrapper, \ AbstractTreeName, AbstractNameDefinition, ContextName from jedi.evaluate.helpers import is_string -from jedi.evaluate.imports import Importer from jedi.evaluate.context.klass import ClassMixin _PROXY_CLASS_TYPES = 'Tuple Generic Protocol Callable Type'.split() @@ -261,6 +262,8 @@ class TypeAlias(HelperContextMixin): if self.evaluator.environment.version_info.major == 2 and module_name == 'builtins': module_name = '__builtin__' + # TODO use evaluator.import_module? + from jedi.evaluate.imports import Importer module, = Importer( self.evaluator, [module_name], self.evaluator.builtins_module ).follow() diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index 67274cd3..a9201318 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -30,6 +30,7 @@ from jedi.evaluate.utils import unite from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate.filters import AbstractNameDefinition from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS +from jedi.evaluate.gradual.typeshed import import_module_decorator class ModuleCache(object): @@ -387,6 +388,7 @@ class JediImportError(Exception): self.import_names = import_names +@import_module_decorator def import_module(evaluator, import_names, parent_module_context, sys_path): """ This method is very similar to importlib's `_gcd_import`. diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index a85e9d9d..d2a7638c 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -26,7 +26,7 @@ from parso import ParserSyntaxError, parse from jedi._compatibility import force_unicode from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS -from jedi.evaluate.context.typing import TypeVar, AnnotatedClass, \ +from jedi.evaluate.gradual.typing import TypeVar, AnnotatedClass, \ AbstractAnnotatedClass from jedi.evaluate.helpers import is_string from jedi import debug diff --git a/jedi/evaluate/syntax_tree.py b/jedi/evaluate/syntax_tree.py index 8ef5697b..1f1dbdf8 100644 --- a/jedi/evaluate/syntax_tree.py +++ b/jedi/evaluate/syntax_tree.py @@ -25,6 +25,7 @@ from jedi.evaluate.finder import NameFinder from jedi.evaluate.helpers import is_string, is_literal, is_number, is_compiled from jedi.evaluate.compiled.access import COMPARISON_OPERATORS from jedi.evaluate.cache import evaluator_method_cache +from jedi.evaluate.gradual.typeshed import VersionInfo def _limit_context_infers(func): @@ -495,7 +496,6 @@ def _eval_comparison_part(evaluator, context, left, operator, right): bool_ = operation(left, right) return ContextSet([_bool_to_context(evaluator, bool_)]) - from jedi.plugins.typeshed import VersionInfo if isinstance(left, VersionInfo): version_info = _get_tuple_ints(right) if version_info is not None: diff --git a/jedi/plugins/__init__.py b/jedi/plugins/__init__.py index c5a188ae..3aa48aac 100644 --- a/jedi/plugins/__init__.py +++ b/jedi/plugins/__init__.py @@ -1,5 +1,4 @@ from jedi.plugins.stdlib import StdlibPlugin -from jedi.plugins.typeshed import TypeshedPlugin from jedi.plugins.flask import FlaskPlugin @@ -35,5 +34,4 @@ class _PluginCallbacks(object): plugin_manager = _PluginManager([ StdlibPlugin, FlaskPlugin, - TypeshedPlugin, ]) diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index 11d019a7..faaa443a 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -9,7 +9,7 @@ from pytest import raises from parso import cache from jedi import preload_module -from jedi.plugins import typeshed +from jedi.evaluate.gradual import typeshed def test_preload_modules(): diff --git a/test/test_plugin/test_stub.py b/test/test_plugin/test_stub.py index 7a75bbe8..fee99f3f 100644 --- a/test/test_plugin/test_stub.py +++ b/test/test_plugin/test_stub.py @@ -1,6 +1,6 @@ import os -from jedi.plugins import typeshed +from jedi.evaluate.gradual import typeshed from jedi.evaluate.context import TreeInstance, BoundMethod, FunctionContext from parso.utils import PythonVersionInfo from jedi.evaluate.filters import TreeNameDefinition diff --git a/test/test_settings.py b/test/test_settings.py index 1a313b9d..c683531b 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -3,7 +3,7 @@ import pytest from jedi import settings from jedi.evaluate.filters import ContextName from jedi.evaluate.compiled import CompiledContextName -from jedi.plugins.typeshed import StubOnlyModuleContext +from jedi.evaluate.gradual.typeshed import StubOnlyModuleContext @pytest.fixture()