Make goto_definitions a lot simpler.

This commit is contained in:
Dave Halter
2016-06-11 23:13:04 +02:00
parent 9930ab5056
commit c82691a12b
12 changed files with 107 additions and 80 deletions

View File

@@ -183,41 +183,9 @@ class Script(object):
:rtype: list of :class:`classes.Definition`
"""
def resolve_import_paths(definitions):
new_defs = list(definitions)
for s in definitions:
if isinstance(s, imports.ImportWrapper):
new_defs.remove(s)
new_defs += resolve_import_paths(set(s.follow()))
return new_defs
c = helpers.ContextResults(self._evaluator, self.source, self._get_module(), self._pos)
definitions = c.get_results()
goto_path = self._user_context.get_path_under_cursor()
context = self._user_context.get_reverse_context()
definitions = []
if next(context) in ('class', 'def'):
definitions = [self._evaluator.wrap(self._parser.user_scope())]
else:
# Fetch definition of callee, if there's no path otherwise.
if not goto_path:
definitions = [signature._definition
for signature in self.call_signatures()]
if re.match('\w[\w\d_]*$', goto_path) and not definitions:
user_stmt = self._parser.user_stmt()
if user_stmt is not None and user_stmt.type == 'expr_stmt':
for name in user_stmt.get_defined_names():
if name.start_pos <= self._pos <= name.end_pos:
# TODO scaning for a name and then using it should be
# the default.
definitions = self._evaluator.goto_definition(name)
if not definitions and goto_path:
definitions = inference.type_inference(
self._evaluator, self._parser, self._user_context,
self._pos, goto_path
)
definitions = resolve_import_paths(definitions)
names = [s.name for s in definitions]
defs = [classes.Definition(self._evaluator, name) for name in names]
# The additional set here allows the definitions to become unique in an

View File

@@ -183,7 +183,7 @@ class Completion:
else:
scopes = list(inference.type_inference(
self._evaluator, self._parser, self._user_context,
self._pos, completion_parts.path, is_completion=True
self._pos, completion_parts.path
))
completion_names = []
debug.dbg('possible completion scopes: %s', scopes)

View File

@@ -212,3 +212,70 @@ def importer_from_error_statement(error_statement, pos):
only_modules = False
return names, level, only_modules, unfinished_dotted
class ContextResults():
def __init__(self, evaluator, source, module, pos):
self._evaluator = evaluator
self._module = module
self._source = source
self._pos = pos
def _on_defining_name(self, leaf):
return [self._evaluator.wrap(self._parser.user_scope())]
def get_results(self):
'''
try:
stack = get_stack_at_position(self._evaluator.grammar, self._source, self._module, self._leaf.end_pos)
except OnErrorLeaf:
return []
'''
name = self._module.name_for_position(self._pos)
if name is not None:
return self._evaluator.goto_definition(name)
leaf = self._module.get_leaf_for_position(self._pos)
if leaf is None:
return []
if leaf.parent.type == 'atom':
return self._evaluator.eval_element(leaf.parent)
if leaf.parent.type == 'trailer':
return self._evaluator.eval_element(leaf.parent.parent)
return []
symbol_names = list(stack.get_node_names(self._evaluator.grammar))
nodes = list(stack.get_nodes())
if "import_stmt" in symbol_names:
level = 0
only_modules = True
level, names = self._parse_dotted_names(nodes)
if "import_from" in symbol_names:
if 'import' in nodes:
only_modules = False
else:
assert "import_name" in symbol_names
completion_names += self._get_importer_names(
names,
level,
only_modules
)
elif nodes[-2] in ('as', 'def', 'class'):
# No completions for ``with x as foo`` and ``import x as foo``.
# Also true for defining names as a class or function.
return self._on_defining_name(self._leaf)
else:
completion_names += self._simple_complete(completion_parts)
return
class GotoDefinition(ContextResults):
def _():
definitions = inference.type_inference(
self._evaluator, self._parser, self._user_context,
self._pos, goto_path
)

View File

@@ -3,50 +3,29 @@ This module has helpers for doing type inference on strings. It is needed,
because we still want to infer types where the syntax is invalid.
"""
from jedi import debug
from jedi.api import helpers
from jedi.parser import tree
from jedi.parser import Parser, ParseError
from jedi.evaluate import imports
from jedi.evaluate.cache import memoize_default
from jedi.api import helpers
def type_inference(evaluator, parser, user_context, position, dotted_path, is_completion=False):
def goto_checks(evaluator, parser, user_context, position, dotted_path, follow_types=False):
module = evaluator.wrap(parser.module())
stack = helpers.get_stack_at_position(evaluator.grammar, self._source, module, position)
stack
def type_inference(evaluator, parser, user_context, position, dotted_path):
"""
Base for completions/goto. Basically it returns the resolved scopes
under cursor.
"""
debug.dbg('start: %s in %s', dotted_path, parser.user_scope())
user_stmt = parser.user_stmt_with_whitespace()
if not user_stmt and len(dotted_path.split('\n')) > 1:
# If the user_stmt is not defined and the dotted_path is multi line,
# something's strange. Most probably the backwards tokenizer
# matched to much.
# Just parse one statement, take it and evaluate it.
eval_stmt = get_under_cursor_stmt(evaluator, parser, dotted_path, position)
if eval_stmt is None:
return []
if isinstance(user_stmt, tree.Import) and not is_completion:
i, _ = helpers.get_on_import_stmt(evaluator, user_context,
user_stmt, is_completion)
if i is None:
return []
scopes = [i]
else:
# Just parse one statement, take it and evaluate it.
eval_stmt = get_under_cursor_stmt(evaluator, parser, dotted_path, position)
if eval_stmt is None:
return []
if not is_completion:
module = evaluator.wrap(parser.module())
names, level, _, _ = helpers.check_error_statements(module, position)
if names:
names = [str(n) for n in names]
i = imports.Importer(evaluator, names, module, level)
return i.follow()
scopes = evaluator.eval_element(eval_stmt)
return scopes
return evaluator.eval_element(eval_stmt)
@memoize_default(evaluator_is_first_arg=True)

View File

@@ -441,16 +441,19 @@ class Evaluator(object):
def goto_definition(self, name):
# TODO rename to goto_definitions
def_ = name.get_definition()
if def_.type == 'expr_stmt' and name in def_.get_defined_names():
types = self.eval_statement(def_, name)
elif def_.type == 'for_stmt':
container_types = self.eval_element(def_.children[3])
for_types = iterable.py__iter__types(self, container_types, def_.children[3])
types = finder.check_tuple_assignments(self, for_types, name)
else:
call = helpers.call_of_name(name)
types = self.eval_element(call)
return types
is_simple_name = name.parent.type not in ('power', 'trailer')
if is_simple_name:
if def_.type == 'expr_stmt' and name in def_.get_defined_names():
return self.eval_statement(def_, name)
elif def_.type == 'for_stmt':
container_types = self.eval_element(def_.children[3])
for_types = iterable.py__iter__types(self, container_types, def_.children[3])
return finder.check_tuple_assignments(self, for_types, name)
elif def_.type == 'import_from':
return imports.ImportWrapper(self, name).follow()
call = helpers.call_of_name(name)
return self.eval_element(call)
def goto(self, name):
def resolve_implicit_imports(names):

View File

@@ -560,7 +560,7 @@ class BaseNode(Base):
except AttributeError:
return c
raise ValueError("Position does not exist.")
return None
@Python3Method
def get_statement_for_position(self, pos):

View File

@@ -125,6 +125,9 @@ f
# -----------------
# unnessecary braces
# -----------------
a = (1)
#? int()
a
#? int()
(1)
#? int()

View File

@@ -131,6 +131,8 @@ A().addition
A().addition = None
#? 8 int()
A(1).addition = None
#? 1 A
A(1).addition = None
a = A()
#? 8 int()
a.addition = None

View File

@@ -6,6 +6,9 @@ Fallback to callee definition when definition not found.
"""Parenthesis closed at next line."""
# Ignore these definitions for a little while, not sure if we really want them.
# python <= 2.7
#? isinstance
isinstance(
)

View File

@@ -202,8 +202,9 @@ default_function()
def a():
l = 3
def func_b():
#? str()
l = ''
#? str()
l
#? ['func_b']
func_b
#? int()

View File

@@ -75,6 +75,7 @@ def scope_from_import_variable():
without the use of ``sys.modules`` modifications (e.g. ``os.path`` see also
github issue #213 for clarification.
"""
a = 3
#?
from import_tree.mod2.fake import a
#?

View File

@@ -102,7 +102,7 @@ if isi
try:
except TypeError:
#? str()
""
str()
def break(): pass
# wrong ternary expression