diff --git a/jedi/api/completion.py b/jedi/api/completion.py index ff4f5278..a2ac4d04 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -18,7 +18,8 @@ from jedi.inference import imports from jedi.inference.base_value import ValueSet from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names from jedi.inference.context import get_global_filters -from jedi.inference.value import TreeInstance, ModuleValue +from jedi.inference.value import TreeInstance +from jedi.inference.docstring_utils import DocstringModule from jedi.inference.names import ParamNameWrapper, SubModuleName from jedi.inference.gradual.conversion import convert_values, convert_names from jedi.parser_utils import cut_value_at_position @@ -462,12 +463,12 @@ class Completion: def _complete_code_lines(self, code_lines): module_node = self._inference_state.grammar.parse(''.join(code_lines)) - module_value = ModuleValue( - self._inference_state, - module_node, + module_value = DocstringModule( + in_module_context=self._module_context, + inference_state=self._inference_state, + module_node=module_node, code_lines=code_lines, ) - module_value.parent_context = self._module_context return Completion( self._inference_state, module_value.as_context(), diff --git a/jedi/inference/docstring_utils.py b/jedi/inference/docstring_utils.py new file mode 100644 index 00000000..bee0d75e --- /dev/null +++ b/jedi/inference/docstring_utils.py @@ -0,0 +1,21 @@ +from jedi.inference.value import ModuleValue +from jedi.inference.context import ModuleContext + + +class DocstringModule(ModuleValue): + def __init__(self, in_module_context, **kwargs): + super().__init__(**kwargs) + self._in_module_context = in_module_context + + def _as_context(self): + return DocstringModuleContext(self, self._in_module_context) + + +class DocstringModuleContext(ModuleContext): + def __init__(self, module_value, in_module_context): + super().__init__(module_value) + self._in_module_context = in_module_context + + def get_filters(self, origin_scope=None, until_position=None): + yield from super().get_filters(until_position=until_position) + yield from self._in_module_context.get_filters() diff --git a/jedi/inference/docstrings.py b/jedi/inference/docstrings.py index 80afbecf..809974f3 100644 --- a/jedi/inference/docstrings.py +++ b/jedi/inference/docstrings.py @@ -17,12 +17,10 @@ annotations. import re import warnings -from textwrap import dedent from parso import parse, ParserSyntaxError from jedi import debug -from jedi.common import indent_block from jedi.inference.cache import inference_state_method_cache from jedi.inference.base_value import iterator_to_value_set, ValueSet, \ NO_VALUES @@ -182,52 +180,40 @@ def _strip_rst_role(type_str): def _infer_for_statement_string(module_context, string): - code = dedent(""" - def pseudo_docstring_stuff(): - ''' - Create a pseudo function for docstring statements. - Need this docstring so that if the below part is not valid Python this - is still a function. - ''' - {} - """) if string is None: return [] - for element in re.findall(r'((?:\w+\.)*\w+)\.', string): - # Try to import module part in dotted name. - # (e.g., 'threading' in 'threading.Thread'). - string = 'import %s\n' % element + string + potential_imports = re.findall(r'((?:\w+\.)*\w+)\.', string) + # Try to import module part in dotted name. + # (e.g., 'threading' in 'threading.Thread'). + imports = "\n".join(f"import {p}" for p in potential_imports) + string = f'{imports}\n{string}' debug.dbg('Parse docstring code %s', string, color='BLUE') grammar = module_context.inference_state.grammar try: - module = grammar.parse(code.format(indent_block(string)), error_recovery=False) + module = grammar.parse(string, error_recovery=False) except ParserSyntaxError: return [] try: - funcdef = next(module.iter_funcdefs()) - # First pick suite, then simple_stmt and then the node, - # which is also not the last item, because there's a newline. - stmt = funcdef.children[-1].children[-1].children[-2] + # It's not the last item, because that's an end marker. + stmt = module.children[-2] except (AttributeError, IndexError): return [] if stmt.type not in ('name', 'atom', 'atom_expr'): return [] - from jedi.inference.value import FunctionValue - function_value = FunctionValue( - module_context.inference_state, - module_context, - funcdef + # Here we basically use a fake module that also uses the filters in + # the actual module. + from jedi.inference.docstring_utils import DocstringModule + m = DocstringModule( + in_module_context=module_context, + inference_state=module_context.inference_state, + module_node=module, + code_lines=[], ) - func_execution_context = function_value.as_context() - # Use the module of the param. - # TODO this module is not the module of the param in case of a function - # call. In that case it's the module of the function call. - # stuffed with content from a function call. - return list(_execute_types_in_stmt(func_execution_context, stmt)) + return list(_execute_types_in_stmt(m.as_context(), stmt)) def _execute_types_in_stmt(module_context, stmt): diff --git a/jedi/inference/filters.py b/jedi/inference/filters.py index 156f8daa..f6af50dc 100644 --- a/jedi/inference/filters.py +++ b/jedi/inference/filters.py @@ -55,6 +55,10 @@ class FilterWrapper: def _get_definition_names(parso_cache_node, used_names, name_key): + if parso_cache_node is None: + names = used_names.get(name_key, ()) + return tuple(name for name in names if name.is_definition(include_setitem=True)) + try: for_module = _definition_name_cache[parso_cache_node] except KeyError: @@ -88,11 +92,16 @@ class _AbstractUsedNamesFilter(AbstractFilter): # used_names. However that also does not work, because it has a # reference from the module, which itself is referenced by any node # through parents. - self._parso_cache_node = get_parso_cache_node( - module_context.inference_state.latest_grammar - if module_context.is_stub() else module_context.inference_state.grammar, - module_context.py__file__() - ) + path = module_context.py__file__() + if path is None: + # If the path is None, there is no guarantee that parso caches it. + self._parso_cache_node = None + else: + self._parso_cache_node = get_parso_cache_node( + module_context.inference_state.latest_grammar + if module_context.is_stub() else module_context.inference_state.grammar, + path + ) self._used_names = module_context.tree_node.get_used_names() self.parent_context = parent_context