1
0
forked from VimPlug/jedi

Move all the gradual typing stuff into one folder

This commit is contained in:
Dave Halter
2018-12-24 17:40:47 +01:00
parent 025b8bba76
commit e2ab4c060f
14 changed files with 107 additions and 101 deletions

View File

@@ -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:

View File

@@ -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()

View File

@@ -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():

View File

@@ -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():

View File

@@ -0,0 +1,557 @@
import os
import re
from jedi._compatibility import FileNotFoundError
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
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
ContextWrapper, NO_CONTEXTS
from jedi.evaluate.filters import ParserTreeFilter, \
NameWrapper, AbstractFilter, TreeNameDefinition
from jedi.evaluate.context import ModuleContext, FunctionContext, \
MethodContext, ClassContext
from jedi.evaluate.context.function import FunctionMixin
from jedi.evaluate.context.klass import ClassMixin
from jedi.evaluate.context.module import ModuleMixin
from jedi.evaluate.gradual.typing import TypingModuleFilterWrapper, \
TypingModuleName
from jedi.evaluate.compiled.context import CompiledName
from jedi.evaluate.utils import to_list, safe_property
_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')
def _merge_create_stub_map(directories):
map_ = {}
for directory in directories:
map_.update(_create_stub_map(directory))
return map_
def _create_stub_map(directory):
"""
Create a mapping of an importable name in Python to a stub file.
"""
def generate():
try:
listed = os.listdir(directory)
except (FileNotFoundError, OSError):
# OSError is Python 2
return
for entry in listed:
path = os.path.join(directory, entry)
if os.path.isdir(path):
init = os.path.join(path, '__init__.pyi')
if os.path.isfile(init):
yield entry, init
elif entry.endswith('.pyi') and os.path.isfile(path):
name = entry.rstrip('.pyi')
if name != '__init__':
yield name, path
# Create a dictionary from the tuple generator.
return dict(generate())
def _get_typeshed_directories(version_info):
check_version_list = ['2and3', str(version_info.major)]
for base in ['stdlib', 'third_party']:
base = os.path.join(_TYPESHED_PATH, base)
base_list = os.listdir(base)
for base_list_entry in base_list:
match = re.match(r'(\d+)\.(\d+)$', base_list_entry)
if match is not None:
if int(match.group(1)) == version_info.major \
and int(match.group(2)) <= version_info.minor:
check_version_list.append(base_list_entry)
for check_version in check_version_list:
yield os.path.join(base, check_version)
@evaluator_function_cache()
def _load_stub(evaluator, path):
return evaluator.parse(path=path, cache=True, use_latest_grammar=True)
def _merge_modules(context_set, stub_context):
if not context_set:
# If there are no results for normal modules, just
# use a normal context for stub modules and don't
# merge the actual module contexts with stubs.
yield stub_context
return
for context in context_set:
if isinstance(context, ModuleContext):
yield StubModuleContext.create_cached(context.evaluator, context, stub_context)
else:
# TODO do we want this? This includes compiled?!
yield stub_context
_version_cache = {}
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:
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
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)
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:
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
class StubName(NameWrapper):
"""
This name is only here to mix stub names with non-stub names. The idea is
that the user can goto the actual name, but end up on the definition of the
stub when inferring types.
"""
def __init__(self, parent_context, non_stub_name, stub_name):
super(StubName, self).__init__(non_stub_name)
self.parent_context = parent_context
self._stub_name = stub_name
@memoize_method
@iterator_to_context_set
def infer(self):
stub_contexts = self._stub_name.infer()
if not stub_contexts:
for c in self._wrapped_name.infer():
yield c
return
typ = self._wrapped_name.tree_name.parent.type
if typ in ('classdef', 'funcdef'):
actual_context, = self._wrapped_name.infer()
for stub_context in stub_contexts:
if isinstance(stub_context, MethodContext):
assert isinstance(actual_context, MethodContext)
cls = StubMethodContext
elif isinstance(stub_context, FunctionContext):
cls = StubFunctionContext
elif isinstance(stub_context, StubOnlyClass):
cls = StubClassContext
else:
yield stub_context
continue
yield cls.create_cached(
actual_context.evaluator,
self.parent_context,
actual_context,
stub_context,
)
else:
for c in stub_contexts:
yield c
class CompiledStubName(NameWrapper):
def __init__(self, parent_context, compiled_name, stub_name):
super(CompiledStubName, self).__init__(stub_name)
self.parent_context = parent_context
self._compiled_name = compiled_name
@memoize_method
@iterator_to_context_set
def infer(self):
compiled_contexts = self._compiled_name.infer()
stub_contexts = self._wrapped_name.infer()
if not compiled_contexts:
for c in stub_contexts:
yield c
for actual_context in compiled_contexts:
for stub_context in stub_contexts:
if isinstance(stub_context, _CompiledStubContext):
# It's already a stub context, e.g. bytes in Python 2
# behaves this way.
yield stub_context
elif stub_context.is_class():
assert not isinstance(stub_context, CompiledStubClass), \
"%s and %s" % (self._wrapped_name, self._compiled_name)
yield CompiledStubClass.create_cached(
stub_context.evaluator, stub_context, actual_context)
elif stub_context.is_function():
yield CompiledStubFunction.create_cached(
stub_context.evaluator, stub_context, actual_context)
else:
yield stub_context
if not stub_contexts:
yield actual_context
class VersionInfo(ContextWrapper):
pass
class StubOnlyName(TreeNameDefinition):
def infer(self):
inferred = super(StubOnlyName, self).infer()
if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys':
return [VersionInfo(c) for c in inferred]
return [
StubOnlyClass.create_cached(c.evaluator, c) if isinstance(c, ClassContext) else c
for c in inferred
]
class StubOnlyFilter(ParserTreeFilter):
name_class = StubOnlyName
def __init__(self, *args, **kwargs):
self._search_global = kwargs.pop('search_global') # Python 2 :/
super(StubOnlyFilter, self).__init__(*args, **kwargs)
def _is_name_reachable(self, name):
if not super(StubOnlyFilter, self)._is_name_reachable(name):
return False
if not self._search_global:
# Imports in stub files are only public if they have an "as"
# export.
definition = name.get_definition()
if definition.type in ('import_from', 'import_name'):
if name.parent.type not in ('import_as_name', 'dotted_as_name'):
return False
n = name.value
if n.startswith('_') and not (n.startswith('__') and n.endswith('__')):
return False
return True
class StubFilter(AbstractFilter):
"""
Merging names from stubs and non-stubs.
"""
def __init__(self, parent_context, non_stub_filters, stub_filters, add_non_stubs):
self._parent_context = parent_context
self._non_stub_filters = non_stub_filters
self._stub_filters = stub_filters
self._add_non_stubs = add_non_stubs
def get(self, name):
non_stub_names = self._get_names_from_filters(self._non_stub_filters, name)
stub_names = self._get_names_from_filters(self._stub_filters, name)
return self._merge_names(non_stub_names, stub_names)
def values(self):
name_dict = {}
for non_stub_filter in self._non_stub_filters:
for name in non_stub_filter.values():
name_dict.setdefault(name.string_name, []).append(name)
# Try to match the names of stubs with non-stubs. If there's no
# match, just use the stub name. The user will be directed there
# for all API accesses. Otherwise the user will be directed to the
# non-stub positions (see StubName).
for stub_filter in self._stub_filters:
for stub_name in stub_filter.values():
merged_names = self._merge_names(
names=name_dict.get(stub_name.string_name),
stub_names=[stub_name]
)
for merged_name in merged_names:
yield merged_name
def _get_names_from_filters(self, filters, string_name):
return [
name
for filter in filters
for name in filter.get(string_name)
]
@to_list
def _merge_names(self, names, stub_names):
if not stub_names:
if self._add_non_stubs:
return names
return []
if not names:
if isinstance(self._stub_filters[0].context, TypingModuleWrapper):
return [TypingModuleName(n) for n in stub_names]
return stub_names
result = []
# The names are contained in both filters.
for name in names:
for stub_name in stub_names:
if isinstance(self._stub_filters[0].context, TypingModuleWrapper):
stub_name = TypingModuleName(stub_name)
if isinstance(name, CompiledName):
result.append(CompiledStubName(self._parent_context, name, stub_name))
else:
result.append(StubName(self._parent_context, name, stub_name))
return result
def __repr__(self):
return '%s(%s, %s)' % (
self.__class__.__name__,
self._non_stub_filters,
self._stub_filters,
)
class _StubContextFilterMixin(object):
def get_filters(self, search_global=False, until_position=None,
origin_scope=None, **kwargs):
filters = self._wrapped_context.get_filters(
search_global, until_position, origin_scope, **kwargs
)
yield self.stub_context.get_stub_only_filter(
parent_context=self,
# Take the first filter, which is here to filter module contents
# and wrap it.
non_stub_filters=[next(filters)],
search_global=search_global,
until_position=until_position,
origin_scope=origin_scope,
)
for f in filters:
yield f
class StubModuleContext(_StubContextFilterMixin, ModuleMixin, ContextWrapper):
def __init__(self, context, stub_context):
super(StubModuleContext, self).__init__(context)
self.stub_context = stub_context
class StubClassContext(_StubContextFilterMixin, ClassMixin, ContextWrapper):
def __init__(self, parent_context, actual_context, stub_context):
super(StubClassContext, self).__init__(actual_context)
self.parent_context = parent_context
self.stub_context = stub_context
def __getattribute__(self, name):
if name in ('py__getitem__', 'py__simple_getitem__', 'py__bases__',
'execute_annotation', 'list_type_vars', 'get_signatures'):
# getitem is always done in the stub class.
return getattr(self.stub_context, name)
return super(StubClassContext, self).__getattribute__(name)
def define_generics(self, type_var_dict):
if not type_var_dict:
return self
return self.stub_context.define_generics(type_var_dict)
class StubFunctionContext(FunctionMixin, ContextWrapper):
def __init__(self, parent_context, actual_context, stub_context):
super(StubFunctionContext, self).__init__(actual_context)
self.parent_context = parent_context
self.stub_context = stub_context
def get_function_execution(self, arguments=None):
return self.stub_context.get_function_execution(arguments)
def get_signatures(self):
return self.stub_context.get_signatures()
class StubMethodContext(StubFunctionContext):
"""
Both of the stub context and the actual context are a stub method.
"""
@safe_property
def class_context(self):
return StubClassContext.create_cached(
self.evaluator,
self.parent_context,
actual_context=self._wrapped_context.class_context,
stub_context=self.stub_context.class_context
)
class _StubOnlyContextMixin(object):
_add_non_stubs_in_filter = False
def _get_stub_only_filters(self, **filter_kwargs):
return [StubOnlyFilter(
self.evaluator,
context=self,
**filter_kwargs
)]
def get_stub_only_filter(self, parent_context, non_stub_filters, **filter_kwargs):
# Here we remap the names from stubs to the actual module. This is
# important if type inferences is needed in that module.
return StubFilter(
parent_context,
non_stub_filters,
self._get_stub_only_filters(**filter_kwargs),
add_non_stubs=self._add_non_stubs_in_filter,
)
def _get_base_filters(self, filters, search_global=False,
until_position=None, origin_scope=None):
next(filters) # Ignore the first filter and replace it with our own
yield self.get_stub_only_filter(
parent_context=self,
non_stub_filters=list(self._get_first_non_stub_filters()),
search_global=search_global,
until_position=until_position,
origin_scope=origin_scope,
)
for f in filters:
yield f
class StubOnlyModuleContext(_StubOnlyContextMixin, ModuleContext):
_add_non_stubs_in_filter = True
def __init__(self, non_stub_context_set, *args, **kwargs):
super(StubOnlyModuleContext, self).__init__(*args, **kwargs)
self.non_stub_context_set = non_stub_context_set
def _get_first_non_stub_filters(self):
for context in self.non_stub_context_set:
yield next(context.get_filters(search_global=False))
def _get_stub_only_filters(self, search_global, **filter_kwargs):
stub_filters = super(StubOnlyModuleContext, self)._get_stub_only_filters(
search_global=search_global, **filter_kwargs
)
stub_filters += self.iter_star_filters(search_global=search_global)
return stub_filters
def get_filters(self, search_global=False, until_position=None,
origin_scope=None, **kwargs):
filters = super(StubOnlyModuleContext, self).get_filters(
search_global, until_position, origin_scope, **kwargs
)
for f in self._get_base_filters(filters, search_global, until_position, origin_scope):
yield f
class StubOnlyClass(_StubOnlyContextMixin, ClassMixin, ContextWrapper):
pass
class _CompiledStubContext(ContextWrapper):
def __init__(self, stub_context, compiled_context):
super(_CompiledStubContext, self).__init__(stub_context)
self._compiled_context = compiled_context
def py__doc__(self, include_call_signature=False):
doc = self._compiled_context.py__doc__()
if include_call_signature:
call_sig = get_call_signature_for_any(self._wrapped_context.tree_node)
if call_sig is not None:
doc = call_sig + '\n\n' + doc
return doc
class CompiledStubFunction(_CompiledStubContext):
pass
class CompiledStubClass(_StubOnlyContextMixin, _CompiledStubContext, ClassMixin):
def _get_first_non_stub_filters(self):
yield next(self._compiled_context.get_filters(search_global=False))
def get_filters(self, search_global=False, until_position=None,
origin_scope=None, **kwargs):
filters = self._wrapped_context.get_filters(
search_global, until_position, origin_scope, **kwargs
)
for f in self._get_base_filters(filters, search_global, until_position, origin_scope):
yield f
class TypingModuleWrapper(StubOnlyModuleContext):
# TODO should use this instead of the isinstance check
def get_filterss(self, *args, **kwargs):
filters = super(TypingModuleWrapper, self).get_filters(*args, **kwargs)
yield TypingModuleFilterWrapper(next(filters))
for f in filters:
yield f

View File

@@ -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()

View File

@@ -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`.

View File

@@ -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

View File

@@ -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: