diff --git a/jedi/api/completion.py b/jedi/api/completion.py index eff54be2..d5c9182b 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -13,7 +13,7 @@ from jedi.api import keywords from jedi.api.file_name import file_name_completions from jedi.inference import imports from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names -from jedi.inference.filters import get_global_filters +from jedi.inference.context import get_global_filters from jedi.inference.gradual.conversion import convert_values from jedi.parser_utils import cut_value_at_position @@ -222,14 +222,13 @@ class Completion: yield keywords.KeywordName(self._inference_state, k) def _global_completions(self): - value = get_user_context(self._module_context, self._position) - debug.dbg('global completion scope: %s', value) + context = get_user_context(self._module_context, self._position) + debug.dbg('global completion scope: %s', context) flow_scope_node = get_flow_scope_node(self._module_node, self._position) filters = get_global_filters( - self._inference_state, - value, + context, self._position, - origin_scope=flow_scope_node + flow_scope_node ) completion_names = [] for filter in filters: diff --git a/jedi/inference/context.py b/jedi/inference/context.py index dbde14b8..23761ada 100644 --- a/jedi/inference/context.py +++ b/jedi/inference/context.py @@ -1,5 +1,8 @@ from abc import abstractmethod +from parso.tree import search_ancestor +from parso.python.tree import Name + from jedi.inference.filters import ParserTreeFilter, MergedFilter, \ GlobalNameFilter from jedi import parser_utils @@ -19,7 +22,9 @@ class AbstractContext(object): def goto(self, name_or_str, position): from jedi.inference import finder f = finder.NameFinder(self.inference_state, self, self, name_or_str, position) - filters = f.get_global_filters() + filters = _get_global_filters_for_name( + self, name_or_str if isinstance(name_or_str, Name) else None, position, + ) return f.filter_name(filters) def py__getattribute__(self, name_or_str, name_value=None, position=None, @@ -32,7 +37,9 @@ class AbstractContext(object): from jedi.inference import finder f = finder.NameFinder(self.inference_state, self, name_value, name_or_str, position, analysis_errors=analysis_errors) - filters = f.get_global_filters() + filters = _get_global_filters_for_name( + self, name_or_str if isinstance(name_or_str, Name) else None, position, + ) return f.find(filters, attribute_lookup=False) def get_root_context(self): @@ -290,3 +297,90 @@ class CompiledContext(ValueContext): def py__file__(self): return self._value.py__file__() + + +def _get_global_filters_for_name(context, name_or_none, position): + # For functions and classes the defaults don't belong to the + # function and get inferred in the value before the function. So + # make sure to exclude the function/class name. + if name_or_none is not None: + ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef', 'lambdef') + lambdef = None + if ancestor == 'lambdef': + # For lambdas it's even more complicated since parts will + # be inferred later. + lambdef = ancestor + ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef') + if ancestor is not None: + colon = ancestor.children[-2] + if position is not None and position < colon.start_pos: + if lambdef is None or position < lambdef.children[-2].start_pos: + position = ancestor.start_pos + + return get_global_filters(context, position, name_or_none) + + +def get_global_filters(context, until_position, origin_scope): + """ + Returns all filters in order of priority for name resolution. + + For global name lookups. The filters will handle name resolution + themselves, but here we gather possible filters downwards. + + >>> from jedi._compatibility import u, no_unicode_pprint + >>> from jedi import Script + >>> script = Script(u(''' + ... x = ['a', 'b', 'c'] + ... def func(): + ... y = None + ... ''')) + >>> module_node = script._module_node + >>> scope = next(module_node.iter_funcdefs()) + >>> scope + + >>> context = script._get_module_context().create_context(scope) + >>> filters = list(get_global_filters(context, (4, 0), None)) + + First we get the names from the function scope. + + >>> no_unicode_pprint(filters[0]) # doctest: +ELLIPSIS + MergedFilter(, ) + >>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE + ['', + ''] + >>> filters[0]._filters[0]._until_position + (4, 0) + >>> filters[0]._filters[1]._until_position + + Then it yields the names from one level "lower". In this example, this is + the module scope (including globals). + As a side note, you can see, that the position in the filter is None on the + globals filter, because there the whole module is searched. + + >>> list(filters[1].values()) # package modules -> Also empty. + [] + >>> sorted(name.string_name for name in filters[2].values()) # Module attributes + ['__doc__', '__name__', '__package__'] + + Finally, it yields the builtin filter, if `include_builtin` is + true (default). + + >>> list(filters[3].values()) # doctest: +ELLIPSIS + [...] + """ + base_context = context + from jedi.inference.value.function import FunctionExecutionContext + while context is not None: + # Names in methods cannot be resolved within the class. + for filter in context.get_filters( + until_position=until_position, + origin_scope=origin_scope): + yield filter + if isinstance(context, FunctionExecutionContext): + # The position should be reset if the current scope is a function. + until_position = None + + context = context.parent_context + + # Add builtins to the global scope. + yield next(base_context.inference_state.builtins_module.get_filters()) diff --git a/jedi/inference/filters.py b/jedi/inference/filters.py index cb59893b..375711f8 100644 --- a/jedi/inference/filters.py +++ b/jedi/inference/filters.py @@ -339,68 +339,3 @@ def publish_method(method_name, python_version_match=None): dct[method_name] = func, python_version_match return func return decorator - - -def get_global_filters(inference_state, context, until_position, origin_scope): - """ - Returns all filters in order of priority for name resolution. - - For global name lookups. The filters will handle name resolution - themselves, but here we gather possible filters downwards. - - >>> from jedi._compatibility import u, no_unicode_pprint - >>> from jedi import Script - >>> script = Script(u(''' - ... x = ['a', 'b', 'c'] - ... def func(): - ... y = None - ... ''')) - >>> module_node = script._module_node - >>> scope = next(module_node.iter_funcdefs()) - >>> scope - - >>> context = script._get_module_context().create_context(scope) - >>> filters = list(get_global_filters(context.inference_state, context, (4, 0), None)) - - First we get the names from the function scope. - - >>> no_unicode_pprint(filters[0]) # doctest: +ELLIPSIS - MergedFilter(, ) - >>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE - ['', - ''] - >>> filters[0]._filters[0]._until_position - (4, 0) - >>> filters[0]._filters[1]._until_position - - Then it yields the names from one level "lower". In this example, this is - the module scope (including globals). - As a side note, you can see, that the position in the filter is None on the - globals filter, because there the whole module is searched. - - >>> list(filters[1].values()) # package modules -> Also empty. - [] - >>> sorted(name.string_name for name in filters[2].values()) # Module attributes - ['__doc__', '__name__', '__package__'] - - Finally, it yields the builtin filter, if `include_builtin` is - true (default). - - >>> list(filters[3].values()) # doctest: +ELLIPSIS - [...] - """ - from jedi.inference.value.function import FunctionExecutionContext - while context is not None: - # Names in methods cannot be resolved within the class. - for filter in context.get_filters( - until_position=until_position, - origin_scope=origin_scope): - yield filter - if isinstance(context, FunctionExecutionContext): - # The position should be reset if the current scope is a function. - until_position = None - - context = context.parent_context - - # Add builtins to the global scope. - yield next(inference_state.builtins_module.get_filters()) diff --git a/jedi/inference/finder.py b/jedi/inference/finder.py index ece87d36..7c7ccf6e 100644 --- a/jedi/inference/finder.py +++ b/jedi/inference/finder.py @@ -25,7 +25,6 @@ from jedi.inference import flow_analysis from jedi.inference.arguments import TreeArguments from jedi.inference import helpers from jedi.inference.value import iterable -from jedi.inference.filters import get_global_filters from jedi.inference.names import TreeNameDefinition from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.parser_utils import is_scope, get_parent_scope @@ -79,29 +78,6 @@ class NameFinder(object): return types - def get_global_filters(self): - origin_scope = self._name if isinstance(self._name, tree.Name) else None - position = self._position - - # For functions and classes the defaults don't belong to the - # function and get inferred in the value before the function. So - # make sure to exclude the function/class name. - if origin_scope is not None: - ancestor = search_ancestor(origin_scope, 'funcdef', 'classdef', 'lambdef') - lambdef = None - if ancestor == 'lambdef': - # For lambdas it's even more complicated since parts will - # be inferred later. - lambdef = ancestor - ancestor = search_ancestor(origin_scope, 'funcdef', 'classdef') - if ancestor is not None: - colon = ancestor.children[-2] - if position is not None and position < colon.start_pos: - if lambdef is None or position < lambdef.children[-2].start_pos: - position = ancestor.start_pos - - return get_global_filters(self._inference_state, self._context, position, origin_scope) - def filter_name(self, filters): """ Searches names that are defined in a scope (the different