diff --git a/jedi/api/completion.py b/jedi/api/completion.py index e2e2b178..a156000f 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -9,7 +9,7 @@ from jedi.api import classes from jedi.api import helpers from jedi.evaluate import imports from jedi.api import keywords -from jedi.evaluate.helpers import evaluate_call_of_leaf +from jedi.evaluate.helpers import evaluate_call_of_leaf, parse_dotted_names from jedi.evaluate.filters import get_global_filters from jedi.parser_utils import get_statement_of_position @@ -185,7 +185,7 @@ class Completion: # Also true for defining names as a class or function. return list(self._get_class_context_completions(is_function=True)) elif "import_stmt" in nonterminals: - level, names = self._parse_dotted_names(nodes, "import_from" in nonterminals) + level, names = parse_dotted_names(nodes, "import_from" in nonterminals) only_modules = not ("import_from" in nonterminals and 'import' in nodes) completion_names += self._get_importer_names( @@ -240,26 +240,6 @@ class Completion: completion_names += filter.values() return completion_names - def _parse_dotted_names(self, nodes, is_import_from): - level = 0 - names = [] - for node in nodes[1:]: - if node in ('.', '...'): - if not names: - level += len(node.value) - elif node.type == 'dotted_name': - names += node.children[::2] - elif node.type == 'name': - names.append(node) - elif node == ',': - if not is_import_from: - names = [] - else: - # Here if the keyword `import` comes along it stops checking - # for names. - break - return level, names - def _get_importer_names(self, names, level=0, only_modules=True): names = [n.value for n in names] i = imports.Importer(self._evaluator, names, self._module_context, level) diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index e0e89061..5d0dd384 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -290,6 +290,16 @@ class Evaluator(object): if type_ in ('import_from', 'import_name'): return imports.infer_import(context, name) + error_node = tree.search_ancestor(name, 'error_node') + if error_node is not None: + first_name = error_node.get_first_leaf().value + if first_name == 'from': + level, names = helpers.parse_dotted_names( + error_node.children, + is_import_from=True + ) + return imports.Importer(self, names, context.get_root_context(), level).follow() + return helpers.evaluate_call_of_leaf(context, name) def goto(self, context, name): diff --git a/jedi/evaluate/context/module.py b/jedi/evaluate/context/module.py index e1b30a5a..df6d3ac2 100644 --- a/jedi/evaluate/context/module.py +++ b/jedi/evaluate/context/module.py @@ -67,6 +67,7 @@ class SubModuleDictMixin(object): return names + class ModuleMixin(SubModuleDictMixin): def get_filters(self, search_global=False, until_position=None, origin_scope=None): yield MergedFilter( diff --git a/jedi/evaluate/helpers.py b/jedi/evaluate/helpers.py index 189f55b2..9a65eb38 100644 --- a/jedi/evaluate/helpers.py +++ b/jedi/evaluate/helpers.py @@ -239,3 +239,24 @@ def execute_evaluated(context, *value_list): from jedi.evaluate.base_context import ContextSet arguments = ValuesArguments([ContextSet([value]) for value in value_list]) return context.evaluator.execute(context, arguments) + + +def parse_dotted_names(nodes, is_import_from): + level = 0 + names = [] + for node in nodes[1:]: + if node in ('.', '...'): + if not names: + level += len(node.value) + elif node.type == 'dotted_name': + names += node.children[::2] + elif node.type == 'name': + names.append(node) + elif node == ',': + if not is_import_from: + names = [] + else: + # Here if the keyword `import` comes along it stops checking + # for names. + break + return level, names diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index e4dbd9ba..ddffdc03 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -239,11 +239,6 @@ class Importer(object): self._evaluator = evaluator self.level = level self.module_context = module_context - try: - self.file_path = module_context.py__file__() - except AttributeError: - # Can be None for certain compiled modules like 'builtins'. - self.file_path = None self._fixed_sys_path = None self._inference_possible = True @@ -316,10 +311,16 @@ class Importer(object): + sys_path.check_sys_path_modifications(self.module_context) ) - if self.import_path and self.file_path is not None \ - and self._evaluator.environment.version_info.major == 2: - # Python2 uses an old strange way of importing relative imports. - sys_path_mod.append(force_unicode(os.path.dirname(self.file_path))) + if self.import_path: + try: + file_path = self.module_context.py__file__() + except AttributeError: + # Can be None for certain compiled modules like 'builtins'. + file_path = None + else: + if self._evaluator.environment.version_info.major == 2: + # Python2 uses an old strange way of importing relative imports. + sys_path_mod.append(force_unicode(os.path.dirname(file_path))) return sys_path_mod diff --git a/test/completion/goto.py b/test/completion/goto.py index adff012d..f0889712 100644 --- a/test/completion/goto.py +++ b/test/completion/goto.py @@ -120,6 +120,8 @@ import_tree.a #! ['module mod1'] import import_tree.mod1 +#! ['module mod1'] +from import_tree.mod1 #! ['a = 1'] import_tree.mod1.a diff --git a/test/completion/imports.py b/test/completion/imports.py index 69e96cda..bb6dbc9e 100644 --- a/test/completion/imports.py +++ b/test/completion/imports.py @@ -114,6 +114,12 @@ def as_imports(): bar.a +def broken_import(): + import import_tree.mod1 + #? import_tree.mod1 + from import_tree.mod1 + + def test_import_priorities(): """ It's possible to overwrite import paths in an ``__init__.py`` file, by