1
0
forked from VimPlug/jedi

Goto stubs if on definition

This commit is contained in:
Dave Halter
2019-03-31 01:19:35 +01:00
parent 2f1ce2bbf9
commit 2fc53045c7
8 changed files with 134 additions and 85 deletions

View File

@@ -38,6 +38,7 @@ from jedi.evaluate.syntax_tree import tree_name_to_contexts
from jedi.evaluate.context import ModuleContext from jedi.evaluate.context import ModuleContext
from jedi.evaluate.base_context import ContextSet from jedi.evaluate.base_context import ContextSet
from jedi.evaluate.context.iterable import unpack_tuple_to_dict from jedi.evaluate.context.iterable import unpack_tuple_to_dict
from jedi.evaluate.gradual.typeshed import try_to_merge_with_stub
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we # Jedi uses lots and lots of recursion. By setting this a little bit higher, we
# can remove some "maximum recursion depth" errors. # can remove some "maximum recursion depth" errors.
@@ -166,6 +167,9 @@ class Script(object):
code_lines=self._code_lines, code_lines=self._code_lines,
is_package=is_package, is_package=is_package,
) )
module, = try_to_merge_with_stub(
self._evaluator, None, module.string_names, ContextSet([module])
)
self._evaluator.module_cache.add(names, ContextSet([module])) self._evaluator.module_cache.add(names, ContextSet([module]))
return module return module

View File

@@ -84,6 +84,7 @@ from jedi.evaluate.context import ClassContext, FunctionContext, \
from jedi.evaluate.context.iterable import CompForContext from jedi.evaluate.context.iterable import CompForContext
from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \ from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \
eval_node, check_tuple_assignments eval_node, check_tuple_assignments
from jedi.evaluate.gradual.stub_context import with_stub_context_if_possible
def _execute(context, arguments): def _execute(context, arguments):
@@ -273,10 +274,15 @@ class Evaluator(object):
def_ = name.get_definition(import_name_always=True) def_ = name.get_definition(import_name_always=True)
if def_ is not None: if def_ is not None:
type_ = def_.type type_ = def_.type
if type_ == 'classdef': is_classdef = type_ == 'classdef'
return [ClassContext(self, context, name.parent)] if is_classdef or type_ == 'funcdef':
if is_classdef:
c = ClassContext(self, context, name.parent)
else:
c = FunctionContext.from_context(context, name.parent)
return with_stub_context_if_possible(c)
elif type_ == 'funcdef': elif type_ == 'funcdef':
return [FunctionContext.from_context(context, name.parent)] return []
if type_ == 'expr_stmt': if type_ == 'expr_stmt':
is_simple_name = name.parent.type not in ('power', 'trailer') is_simple_name = name.parent.type not in ('power', 'trailer')

View File

@@ -172,6 +172,10 @@ class Context(HelperContextMixin, BaseContext):
debug.warning("Not possible to return the stop iterations of %s", self) debug.warning("Not possible to return the stop iterations of %s", self)
return NO_CONTEXTS return NO_CONTEXTS
def get_qualified_names(self):
# Returns Optional[List[str]]
return None
def iterate_contexts(contexts, contextualized_node=None, is_async=False): def iterate_contexts(contexts, contextualized_node=None, is_async=False):
""" """

View File

@@ -19,7 +19,6 @@ from jedi.evaluate.lazy_context import LazyKnownContexts, LazyKnownContext, \
from jedi.evaluate.context import iterable from jedi.evaluate.context import iterable
from jedi import parser_utils from jedi import parser_utils
from jedi.evaluate.parser_cache import get_yield_exprs from jedi.evaluate.parser_cache import get_yield_exprs
from jedi.evaluate.gradual.annotation import infer_return_types
class LambdaName(AbstractNameDefinition): class LambdaName(AbstractNameDefinition):
@@ -38,7 +37,24 @@ class LambdaName(AbstractNameDefinition):
return ContextSet([self._lambda_context]) return ContextSet([self._lambda_context])
class FunctionMixin(object): class FunctionAndClassMixin(object):
def get_qualified_names(self):
if self.parent_context.is_class():
n = self.parent_context.get_qualified_names()
if n is None:
# This means that the parent class lives within a function.
return None
return n + [self.py__name__()]
elif self.parent_context.is_module():
return [self.py__name__()]
else:
return None
def py__name__(self):
return self.name.string_name
class FunctionMixin(FunctionAndClassMixin):
api_type = u'function' api_type = u'function'
def get_filters(self, search_global=False, until_position=None, origin_scope=None): def get_filters(self, search_global=False, until_position=None, origin_scope=None):
@@ -82,9 +98,6 @@ class FunctionMixin(object):
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments) return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
def py__name__(self):
return self.name.string_name
class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, TreeContext)): class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, TreeContext)):
""" """
@@ -179,6 +192,7 @@ class FunctionExecutionContext(TreeContext):
returns = get_yield_exprs(self.evaluator, funcdef) returns = get_yield_exprs(self.evaluator, funcdef)
else: else:
returns = funcdef.iter_return_stmts() returns = funcdef.iter_return_stmts()
from jedi.evaluate.gradual.annotation import infer_return_types
context_set = infer_return_types(self) context_set = infer_return_types(self)
if context_set: if context_set:
# If there are annotations, prefer them over anything else. # If there are annotations, prefer them over anything else.

View File

@@ -49,6 +49,7 @@ from jedi.evaluate.filters import ParserTreeFilter, TreeNameDefinition, \
from jedi.evaluate.arguments import unpack_arglist from jedi.evaluate.arguments import unpack_arglist
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \ from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
TreeContext, NO_CONTEXTS TreeContext, NO_CONTEXTS
from jedi.evaluate.context.function import FunctionAndClassMixin
def apply_py__get__(context, instance, class_context): def apply_py__get__(context, instance, class_context):
@@ -118,7 +119,7 @@ class ClassFilter(ParserTreeFilter):
return [name for name in names if self._access_possible(name)] return [name for name in names if self._access_possible(name)]
class ClassMixin(object): class ClassMixin(FunctionAndClassMixin):
def is_class(self): def is_class(self):
return True return True
@@ -133,9 +134,6 @@ class ClassMixin(object):
def name(self): def name(self):
return ContextName(self, self.tree_node.name) return ContextName(self, self.tree_node.name)
def py__name__(self):
return self.name.string_name
def get_param_names(self): def get_param_names(self):
for context_ in self.py__getattribute__(u'__init__'): for context_ in self.py__getattribute__(u'__init__'):
if context_.is_function(): if context_.is_function():

View File

@@ -1,7 +1,7 @@
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.parser_utils import get_call_signature_for_any from jedi.parser_utils import get_call_signature_for_any
from jedi.evaluate.utils import safe_property from jedi.evaluate.utils import safe_property
from jedi.evaluate.base_context import ContextWrapper from jedi.evaluate.base_context import ContextWrapper, ContextSet
from jedi.evaluate.context.function import FunctionMixin, FunctionContext, MethodContext from jedi.evaluate.context.function import FunctionMixin, FunctionContext, MethodContext
from jedi.evaluate.context.klass import ClassMixin, ClassContext from jedi.evaluate.context.klass import ClassMixin, ClassContext
from jedi.evaluate.context.module import ModuleMixin, ModuleContext from jedi.evaluate.context.module import ModuleMixin, ModuleContext
@@ -208,17 +208,22 @@ class StubName(NameWrapper):
self._stub_name = stub_name self._stub_name = stub_name
@memoize_method @memoize_method
@iterator_to_context_set
def infer(self): def infer(self):
stub_contexts = self._stub_name.infer() stub_contexts = self._stub_name.infer()
if not stub_contexts: if not stub_contexts:
for c in self._wrapped_name.infer(): return self._wrapped_name.infer()
yield c
return
typ = self._wrapped_name.tree_name.parent.type typ = self._wrapped_name.tree_name.parent.type
# TODO is this if a performance optimization?
if typ in ('classdef', 'funcdef'): if typ in ('classdef', 'funcdef'):
actual_context, = self._wrapped_name.infer() actual_context, = self._wrapped_name.infer()
return _add_stub_if_possible(self.parent_context, actual_context, stub_contexts)
else:
return stub_contexts
@iterator_to_context_set
def _add_stub_if_possible(parent_context, actual_context, stub_contexts):
for stub_context in stub_contexts: for stub_context in stub_contexts:
if isinstance(stub_context, MethodContext): if isinstance(stub_context, MethodContext):
assert isinstance(actual_context, MethodContext) assert isinstance(actual_context, MethodContext)
@@ -232,13 +237,26 @@ class StubName(NameWrapper):
continue continue
yield cls.create_cached( yield cls.create_cached(
actual_context.evaluator, actual_context.evaluator,
self.parent_context, parent_context,
actual_context, actual_context,
stub_context, stub_context,
) )
else:
for c in stub_contexts:
yield c def with_stub_context_if_possible(actual_context):
names = actual_context.get_qualified_names()
stub_module = actual_context.get_root_context().stub_context
if stub_module is None:
return ContextSet([actual_context])
stub_contexts = ContextSet([stub_module])
for name in names:
stub_contexts = stub_contexts.py__getattribute__(name)
return _add_stub_if_possible(
actual_context.parent_context,
actual_context,
stub_contexts,
)
class CompiledStubName(NameWrapper): class CompiledStubName(NameWrapper):

View File

@@ -136,6 +136,12 @@ def import_module_decorator(func):
else: else:
raise raise
return try_to_merge_with_stub(evaluator, parent_module_context,
import_names, context_set)
return wrapper
def try_to_merge_with_stub(evaluator, parent_module_context, import_names, actual_context_set):
import_name = import_names[-1] import_name = import_names[-1]
map_ = None map_ = None
if len(import_names) == 1: if len(import_names) == 1:
@@ -144,7 +150,7 @@ def import_module_decorator(func):
if not parent_module_context.stub_context.is_package: if not parent_module_context.stub_context.is_package:
# Only if it's a package (= a folder) something can be # Only if it's a package (= a folder) something can be
# imported. # imported.
return context_set return actual_context_set
path = parent_module_context.stub_context.py__path__() path = parent_module_context.stub_context.py__path__()
map_ = _merge_create_stub_map(path) map_ = _merge_create_stub_map(path)
@@ -164,7 +170,7 @@ def import_module_decorator(func):
module_cls = StubOnlyModuleContext module_cls = StubOnlyModuleContext
file_name = os.path.basename(path) file_name = os.path.basename(path)
stub_module_context = module_cls( stub_module_context = module_cls(
context_set, evaluator, stub_module_node, actual_context_set, evaluator, stub_module_node,
path=path, path=path,
string_names=import_names, string_names=import_names,
# The code was loaded with latest_grammar, so use # The code was loaded with latest_grammar, so use
@@ -172,8 +178,7 @@ def import_module_decorator(func):
code_lines=get_cached_code_lines(evaluator.latest_grammar, path), code_lines=get_cached_code_lines(evaluator.latest_grammar, path),
is_package=file_name == '__init__.pyi', is_package=file_name == '__init__.pyi',
) )
modules = _merge_modules(context_set, stub_module_context) modules = _merge_modules(actual_context_set, stub_module_context)
return ContextSet(modules) return ContextSet(modules)
# If no stub is found, just return the default. # If no stub is found, just return the default.
return context_set return actual_context_set
return wrapper

View File

@@ -187,24 +187,24 @@ def test_goto_stubs(Script):
@pytest.mark.parametrize( @pytest.mark.parametrize(
'code', [ 'code', [
'import os; os.walk' 'import os; os.walk',
'from collections import Counter; Counter' 'from collections import Counter; Counter',
]) ])
def test_goto_stubs_on_itself(Script, code): def test_goto_stubs_on_itself(Script, code):
""" """
If goto_stubs is used on an identifier in e.g. the stdlib, we should goto If goto_stubs is used on an identifier in e.g. the stdlib, we should goto
the stub of it. the stub of it.
""" """
s = Script() s = Script(code)
os_module, = s.goto_definitions() def_, = s.goto_definitions()
stub = os_module.goto_stubs() stub, = def_.goto_stubs()
script_on_source = Script( script_on_source = Script(
path=os_module.module_path, path=def_.module_path,
line=os_module.line, line=def_.line,
column=os_module.column column=def_.column
) )
definition, = script_on_source.goto_assignments() definition, = script_on_source.goto_definitions()
same_stub, = definition.goto_stubs() same_stub, = definition.goto_stubs()
assert stub.module_path == same_stub.module_path assert stub.module_path == same_stub.module_path
assert stub.line == same_stub.line assert stub.line == same_stub.line