forked from VimPlug/jedi
350 lines
12 KiB
Python
350 lines
12 KiB
Python
import os
|
|
|
|
from jedi.cache import memoize_method
|
|
from jedi.parser_utils import get_call_signature_for_any
|
|
from jedi.evaluate.base_context import ContextWrapper, ContextSet, \
|
|
NO_CONTEXTS, iterator_to_context_set
|
|
from jedi.evaluate.context.klass import ClassMixin, ClassContext
|
|
from jedi.evaluate.context.module import ModuleContext
|
|
from jedi.evaluate.filters import ParserTreeFilter, \
|
|
NameWrapper, TreeNameDefinition
|
|
from jedi.evaluate.utils import to_list
|
|
from jedi.evaluate.gradual.typing import TypingModuleFilterWrapper, AnnotatedClass
|
|
|
|
|
|
class _StubOnlyContextMixin(object):
|
|
_add_non_stubs_in_filter = False
|
|
|
|
def is_stub(self):
|
|
return True
|
|
|
|
def _get_stub_only_filters(self, **filter_kwargs):
|
|
return [StubOnlyFilter(
|
|
self.evaluator,
|
|
context=self,
|
|
**filter_kwargs
|
|
)]
|
|
|
|
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
|
|
stub_only_filters = self._get_stub_only_filters(
|
|
search_global=search_global,
|
|
until_position=until_position,
|
|
origin_scope=origin_scope,
|
|
)
|
|
for f in stub_only_filters:
|
|
yield f
|
|
|
|
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
|
|
|
|
def _iter_modules(self, path):
|
|
dirs = os.listdir(path)
|
|
for name in dirs:
|
|
if os.path.isdir(os.path.join(path, name)):
|
|
yield (None, name, True)
|
|
if name.endswith('.pyi'):
|
|
yield (None, name[:-4], True)
|
|
return []
|
|
|
|
|
|
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 is_stub(self):
|
|
return True
|
|
|
|
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):
|
|
def get_filters(self, *args, **kwargs):
|
|
filters = super(TypingModuleWrapper, self).get_filters(*args, **kwargs)
|
|
yield TypingModuleFilterWrapper(next(filters))
|
|
for f in filters:
|
|
yield f
|
|
|
|
|
|
def goto_with_stubs_if_possible(name):
|
|
return [name]
|
|
# XXX
|
|
root = name.parent_context.get_root_context()
|
|
stub = root.get_root_context().stub_context
|
|
if stub is None:
|
|
return [name]
|
|
|
|
qualified_names = name.parent_context.get_qualified_names()
|
|
if qualified_names is None:
|
|
return [name]
|
|
|
|
stub_contexts = ContextSet([stub])
|
|
for n in qualified_names:
|
|
stub_contexts = stub_contexts.py__getattribute__(n)
|
|
names = stub_contexts.py__getattribute__(name.string_name, is_goto=True)
|
|
return [
|
|
n
|
|
for n in names
|
|
if n.start_pos == name.start_pos and n.parent_context == name.parent_context
|
|
] or [name]
|
|
|
|
|
|
def goto_non_stub(parent_context, tree_name):
|
|
contexts = stub_to_actual_context_set(parent_context)
|
|
return contexts.py__getattribute__(tree_name, is_goto=True)
|
|
|
|
|
|
def stub_to_actual_context_set(stub_context, ignore_compiled=False):
|
|
stub_module = stub_context.get_root_context()
|
|
if not stub_module.is_stub():
|
|
return ContextSet([stub_context])
|
|
|
|
qualified_names = stub_context.get_qualified_names()
|
|
if qualified_names is None:
|
|
return NO_CONTEXTS
|
|
|
|
assert isinstance(stub_module, StubOnlyModuleContext), stub_module
|
|
non_stubs = stub_module.non_stub_context_set
|
|
if ignore_compiled:
|
|
non_stubs = non_stubs.filter(lambda c: not c.is_compiled())
|
|
for name in qualified_names:
|
|
non_stubs = non_stubs.py__getattribute__(name)
|
|
return non_stubs
|
|
|
|
|
|
def try_stubs_to_actual_context_set(stub_contexts, prefer_stub_to_compiled=False):
|
|
return ContextSet.from_sets(
|
|
stub_to_actual_context_set(stub_context, ignore_compiled=prefer_stub_to_compiled)
|
|
or ContextSet([stub_context])
|
|
for stub_context in stub_contexts
|
|
)
|
|
|
|
|
|
@to_list
|
|
def try_stub_to_actual_names(names, prefer_stub_to_compiled=False):
|
|
for name in names:
|
|
# Using the tree_name is better, if it's available, becuase no
|
|
# information is lost. If the name given is defineda as `foo: int` we
|
|
# would otherwise land on int, which is not what we want. We want foo
|
|
# from the non-stub module.
|
|
if name.tree_name is None:
|
|
actual_contexts = ContextSet.from_sets(
|
|
stub_to_actual_context_set(c) for c in name.infer()
|
|
)
|
|
actual_contexts = actual_contexts.filter(lambda c: not c.is_compiled())
|
|
if actual_contexts:
|
|
for s in actual_contexts:
|
|
yield s.name
|
|
else:
|
|
yield name
|
|
else:
|
|
parent_context = name.parent_context
|
|
if not parent_context.is_stub():
|
|
yield name
|
|
continue
|
|
|
|
contexts = stub_to_actual_context_set(parent_context)
|
|
if prefer_stub_to_compiled:
|
|
# We don't really care about
|
|
contexts = contexts.filter(lambda c: not c.is_compiled())
|
|
|
|
new_names = contexts.py__getattribute__(name.tree_name, is_goto=True)
|
|
|
|
if new_names:
|
|
for n in new_names:
|
|
yield n
|
|
else:
|
|
yield name
|
|
|
|
|
|
def _load_or_get_stub_module(evaluator, names):
|
|
return evaluator.stub_module_cache.get(names)
|
|
|
|
|
|
def load_stubs(context):
|
|
root_context = context.get_root_context()
|
|
stub_module = _load_or_get_stub_module(
|
|
context.evaluator,
|
|
root_context.string_names
|
|
)
|
|
if stub_module is None:
|
|
return NO_CONTEXTS
|
|
|
|
qualified_names = context.get_qualified_names()
|
|
if qualified_names is None:
|
|
return NO_CONTEXTS
|
|
|
|
stub_contexts = ContextSet([stub_module])
|
|
for name in qualified_names:
|
|
stub_contexts = stub_contexts.py__getattribute__(name)
|
|
|
|
if isinstance(context, AnnotatedClass):
|
|
return ContextSet([
|
|
context.annotate_other_class(c) if c.is_class() else c
|
|
for c in stub_contexts
|
|
])
|
|
return stub_contexts
|
|
|
|
|
|
def _load_stub_module(module):
|
|
if module.is_stub():
|
|
return module
|
|
from jedi.evaluate.gradual.typeshed import _try_to_load_stub
|
|
return _try_to_load_stub(
|
|
module.evaluator,
|
|
ContextSet([module]),
|
|
parent_module_context=None,
|
|
import_names=module.string_names
|
|
)
|
|
|
|
|
|
def name_to_stub(name):
|
|
return ContextSet.from_sets(to_stub(c) for c in name.infer())
|
|
|
|
|
|
def to_stub(context):
|
|
if context.is_stub():
|
|
return ContextSet([context])
|
|
|
|
qualified_names = context.get_qualified_names()
|
|
stub_module = _load_stub_module(context.get_root_context())
|
|
if stub_module is None or qualified_names is None:
|
|
return NO_CONTEXTS
|
|
|
|
stub_contexts = ContextSet([stub_module])
|
|
for name in qualified_names:
|
|
stub_contexts = stub_contexts.py__getattribute__(name)
|
|
return stub_contexts
|
|
|
|
|
|
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 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 VersionInfo(ContextWrapper):
|
|
pass
|