diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 1defb6c2..ed1af143 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -29,6 +29,7 @@ from jedi.evaluate import representation as er from jedi.evaluate import compiled from jedi.evaluate import imports from jedi.evaluate import helpers +from jedi.evaluate.finder import get_names_of_scope class NotFoundError(Exception): @@ -170,8 +171,9 @@ class Script(object): scopes = list(self._prepare_goto(path, True)) except NotFoundError: scopes = [] - scope_generator = self._evaluator.get_names_of_scope( - self._parser.user_scope(), self._pos) + scope_generator = get_names_of_scope(self._evaluator, + self._parser.user_scope(), + self._pos) completions = [] for scope, name_list in scope_generator: for c in name_list: diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 5dd086cc..41989ac1 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -16,6 +16,7 @@ from jedi.evaluate import iterable from jedi.evaluate import imports from jedi.evaluate import compiled from jedi.api import keywords +from jedi.evaluate.finder import get_names_of_scope def clear_caches(): @@ -550,8 +551,8 @@ def defined_names(evaluator, scope): :type scope: Scope :rtype: list of Definition """ - pair = next(evaluator.get_names_of_scope( - scope, star_search=False, include_builtin=False), None) + pair = next(get_names_of_scope(evaluator, scope, star_search=False, + include_builtin=False), None) names = pair[1] if pair else [] return [Definition(evaluator, d) for d in sorted(names, key=lambda s: s.start_pos)] diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index ed6ba61b..e5c54ea8 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -68,10 +68,9 @@ backtracking algorithm. .. todo:: nonlocal statement, needed or can be ignored? (py3k) """ -import sys import itertools -from jedi._compatibility import next, hasattr, unicode, reraise +from jedi._compatibility import next, hasattr, unicode from jedi import common from jedi.parser import representation as pr from jedi import debug @@ -91,92 +90,6 @@ class Evaluator(object): self.recursion_detector = recursion.RecursionDetector() self.execution_recursion_detector = recursion.ExecutionRecursionDetector() - def get_names_of_scope(self, scope, position=None, star_search=True, - include_builtin=True): - """ - Get all completions (names) possible for the current scope. - The star search option is only here to provide an optimization. Otherwise - the whole thing would probably start a little recursive madness. - - This function is used to include names from outer scopes. For example, - when the current scope is function: - - >>> from jedi.parser import Parser - >>> parser = Parser(''' - ... x = ['a', 'b', 'c'] - ... def func(): - ... y = None - ... ''') - >>> scope = parser.module.subscopes[0] - >>> scope - - - `get_names_of_scope` is a generator. First it yields names from - most inner scope. - - >>> pairs = list(Evaluator().get_names_of_scope(scope)) - >>> pairs[0] - (, []) - - Then it yield the names from one level outer scope. For this - example, this is the most outer scope. - - >>> pairs[1] - (, [, ]) - - Finally, it yields names from builtin, if `include_builtin` is - true (default). - - >>> pairs[2] #doctest: +ELLIPSIS - (, [, ...]) - - :rtype: [(pr.Scope, [pr.Name])] - :return: Return an generator that yields a pair of scope and names. - """ - in_func_scope = scope - non_flow = scope.get_parent_until(pr.Flow, reverse=True) - while scope: - if isinstance(scope, pr.SubModule) and scope.parent: - # we don't want submodules to report if we have modules. - scope = scope.parent - continue - # `pr.Class` is used, because the parent is never `Class`. - # Ignore the Flows, because the classes and functions care for that. - # InstanceElement of Class is ignored, if it is not the start scope. - if not (scope != non_flow and scope.isinstance(pr.Class) - or scope.isinstance(pr.Flow) - or scope.isinstance(er.Instance) - and non_flow.isinstance(er.Function) - or isinstance(scope, compiled.CompiledObject) - and scope.type() == 'class' and in_func_scope != scope): - try: - if isinstance(scope, er.Instance): - for g in scope.scope_generator(): - yield g - else: - yield scope, finder._get_defined_names_for_position(scope, position, in_func_scope) - except StopIteration: - reraise(common.MultiLevelStopIteration, sys.exc_info()[2]) - if scope.isinstance(pr.ForFlow) and scope.is_list_comp: - # is a list comprehension - yield scope, scope.get_set_vars(is_internal_call=True) - - scope = scope.parent - # This is used, because subscopes (Flow scopes) would distort the - # results. - if scope and scope.isinstance(er.Function, pr.Function, er.FunctionExecution): - in_func_scope = scope - - # Add star imports. - if star_search: - for s in imports.remove_star_imports(self, non_flow.get_parent_until()): - for g in self.get_names_of_scope(s, star_search=False): - yield g - - # Add builtins to the global scope. - if include_builtin: - yield compiled.builtin, compiled.builtin.get_defined_names() - def find_types(self, scope, name_str, position=None, search_global=False, is_goto=False, resolve_decorator=True): """ diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 231f23a9..0a1ff014 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -1,6 +1,7 @@ import copy +import sys -from jedi._compatibility import hasattr, unicode, u +from jedi._compatibility import hasattr, unicode, u, reraise from jedi.parser import representation as pr from jedi import debug from jedi import common @@ -10,6 +11,7 @@ from jedi.evaluate import dynamic from jedi.evaluate import compiled from jedi.evaluate import docstrings from jedi.evaluate import iterable +from jedi.evaluate import imports class NameFinder(object): @@ -27,7 +29,7 @@ class NameFinder(object): def scopes(self, search_global=False): if search_global: - return self._evaluator.get_names_of_scope(self.scope, self.position) + return get_names_of_scope(self._evaluator, self.scope, self.position) else: if isinstance(self.scope, er.Instance): return self.scope.scope_generator() @@ -361,6 +363,93 @@ def _get_defined_names_for_position(scope, position=None, start_scope=None): return names_new +def get_names_of_scope(evaluator, scope, position=None, star_search=True, include_builtin=True): + """ + Get all completions (names) possible for the current scope. The star search + option is only here to provide an optimization. Otherwise the whole thing + would probably start a little recursive madness. + + This function is used to include names from outer scopes. For example, when + the current scope is function: + + >>> from jedi.parser import Parser + >>> parser = Parser(''' + ... x = ['a', 'b', 'c'] + ... def func(): + ... y = None + ... ''') + >>> scope = parser.module.subscopes[0] + >>> scope + + + `get_names_of_scope` is a generator. First it yields names from most inner + scope. + + >>> from jedi.evaluate import Evaluator + >>> pairs = list(get_names_of_scope(Evaluator(), scope)) + >>> pairs[0] + (, []) + + Then it yield the names from one level outer scope. For this example, this + is the most outer scope. + + >>> pairs[1] + (, [, ]) + + Finally, it yields names from builtin, if `include_builtin` is + true (default). + + >>> pairs[2] #doctest: +ELLIPSIS + (, [, ...]) + + :rtype: [(pr.Scope, [pr.Name])] + :return: Return an generator that yields a pair of scope and names. + """ + in_func_scope = scope + non_flow = scope.get_parent_until(pr.Flow, reverse=True) + while scope: + if isinstance(scope, pr.SubModule) and scope.parent: + # we don't want submodules to report if we have modules. + scope = scope.parent + continue + # `pr.Class` is used, because the parent is never `Class`. + # Ignore the Flows, because the classes and functions care for that. + # InstanceElement of Class is ignored, if it is not the start scope. + if not (scope != non_flow and scope.isinstance(pr.Class) + or scope.isinstance(pr.Flow) + or scope.isinstance(er.Instance) + and non_flow.isinstance(er.Function) + or isinstance(scope, compiled.CompiledObject) + and scope.type() == 'class' and in_func_scope != scope): + try: + if isinstance(scope, er.Instance): + for g in scope.scope_generator(): + yield g + else: + yield scope, _get_defined_names_for_position(scope, position, in_func_scope) + except StopIteration: + reraise(common.MultiLevelStopIteration, sys.exc_info()[2]) + if scope.isinstance(pr.ForFlow) and scope.is_list_comp: + # is a list comprehension + yield scope, scope.get_set_vars(is_internal_call=True) + + scope = scope.parent + # This is used, because subscopes (Flow scopes) would distort the + # results. + if scope and scope.isinstance(er.Function, pr.Function, er.FunctionExecution): + in_func_scope = scope + + # Add star imports. + if star_search: + for s in imports.remove_star_imports(evaluator, non_flow.get_parent_until()): + for g in get_names_of_scope(evaluator, s, star_search=False): + yield g + + # Add builtins to the global scope. + if include_builtin: + yield compiled.builtin, compiled.builtin.get_defined_names() + + def _assign_tuples(tup, results, seek_name): """ This is a normal assignment checker. In python functions and other things diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index 02f3cedc..ad5727ce 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -131,8 +131,9 @@ class ImportPath(pr.Base): names.append(pr.Name(self.GlobalNamespace, [('path', p)], p, p, self.import_stmt)) continue - for s, scope_names in self._evaluator.get_names_of_scope(scope, - include_builtin=False): + from jedi.evaluate import finder + for s, scope_names in finder.get_names_of_scope(self._evaluator, + scope, include_builtin=False): for n in scope_names: if self.import_stmt.from_ns is None \ or self.is_partial_import: