forked from VimPlug/jedi
Goto stubs if on definition
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user