diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 02dd735c..09947ada 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -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 diff --git a/jedi/api/completion.py b/jedi/api/completion.py index 7d16b481..00d1f5bf 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -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) diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index a92d8e25..9dcf4cd8 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -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 + ) diff --git a/jedi/api/inference.py b/jedi/api/inference.py index 345ddeec..84ef1815 100644 --- a/jedi/api/inference.py +++ b/jedi/api/inference.py @@ -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) diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index f0e44900..013d6f2d 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -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): diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index b20cf2b8..a4890d33 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -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): diff --git a/test/completion/arrays.py b/test/completion/arrays.py index 8cb9f3a1..e2ec70d8 100644 --- a/test/completion/arrays.py +++ b/test/completion/arrays.py @@ -125,6 +125,9 @@ f # ----------------- # unnessecary braces # ----------------- +a = (1) +#? int() +a #? int() (1) #? int() diff --git a/test/completion/classes.py b/test/completion/classes.py index 9bb5d99d..0b1f72b3 100644 --- a/test/completion/classes.py +++ b/test/completion/classes.py @@ -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 diff --git a/test/completion/definition.py b/test/completion/definition.py index 19934b87..ab563ad8 100644 --- a/test/completion/definition.py +++ b/test/completion/definition.py @@ -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( ) diff --git a/test/completion/functions.py b/test/completion/functions.py index f56010bf..c1a40e56 100644 --- a/test/completion/functions.py +++ b/test/completion/functions.py @@ -202,8 +202,9 @@ default_function() def a(): l = 3 def func_b(): - #? str() l = '' + #? str() + l #? ['func_b'] func_b #? int() diff --git a/test/completion/imports.py b/test/completion/imports.py index 2e5509d1..e373027a 100644 --- a/test/completion/imports.py +++ b/test/completion/imports.py @@ -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 #? diff --git a/test/completion/invalid.py b/test/completion/invalid.py index e23d4ff8..7e952cf2 100644 --- a/test/completion/invalid.py +++ b/test/completion/invalid.py @@ -102,7 +102,7 @@ if isi try: except TypeError: #? str() - "" + str() def break(): pass # wrong ternary expression