diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 583c4648..9344adc7 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -9,6 +9,7 @@ import warnings from parso.python.tree import search_ancestor from jedi import settings +from jedi import debug from jedi.evaluate.utils import unite from jedi.cache import memoize_method from jedi.evaluate import imports @@ -16,8 +17,8 @@ from jedi.evaluate import compiled from jedi.evaluate.imports import ImportName from jedi.evaluate.context import FunctionExecutionContext from jedi.evaluate.gradual.typeshed import StubModuleContext -from jedi.evaluate.gradual.conversion import name_to_stub, \ - stub_to_actual_context_set, try_stubs_to_actual_context_set +from jedi.evaluate.gradual.conversion import try_stub_to_actual_names, \ + stub_to_actual_context_set, try_stubs_to_actual_context_set, actual_to_stub_names from jedi.api.keywords import KeywordName @@ -280,26 +281,30 @@ class BaseDefinition(object): return False return all(c.is_stub() for c in self._name.infer()) - def goto_stubs(self): + def goto_assignments(self, **kwargs): # Python 2... + return self._goto_assignments(**kwargs) + + def _goto_assignments(self, only_stubs=False, prefer_stubs=False): + assert not (only_stubs and prefer_stubs) + if not self._name.is_context_name: return [] - if self.is_stub(): - return [self] - - return [ - Definition(self._evaluator, stub_def.name) - for stub_def in name_to_stub(self._name) - ] - - def goto_assignments(self): - if not self._name.is_context_name: - return [] + names = self._name.goto() + if only_stubs or prefer_stubs: + names = actual_to_stub_names(names, fallback_to_actual=prefer_stubs) + else: + names = try_stub_to_actual_names(names, prefer_stub_to_compiled=True) return [self if n == self._name else Definition(self._evaluator, n) - for n in self._name.goto()] + for n in names] + + def infer(self, **kwargs): # Python 2... + return self._infer(**kwargs) + + def _infer(self, only_stubs=False, prefer_stubs=False): + assert not (only_stubs and prefer_stubs) - def infer(self): if not self._name.is_context_name: return [] diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index fb84268f..9c1b21ed 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -85,7 +85,7 @@ from jedi.evaluate.context.iterable import CompForContext from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \ eval_node, check_tuple_assignments from jedi.evaluate.gradual.conversion import try_stub_to_actual_names, \ - try_stubs_to_actual_context_set + actual_to_stub_names, try_stubs_to_actual_context_set def _execute(context, arguments): @@ -322,11 +322,6 @@ class Evaluator(object): return None def goto(self, context, name): - names = self._goto(context, name) - names = try_stub_to_actual_names(names, prefer_stub_to_compiled=True) - return names - - def _goto(self, context, name): definition = name.get_definition(import_name_always=True) if definition is not None: type_ = definition.type diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index ac019daa..f5737336 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -263,6 +263,10 @@ class CompiledName(AbstractNameDefinition): self.parent_context = parent_context self.string_name = name + def _get_qualified_names(self): + parent_qualified_names = self.parent_context.get_qualified_names() + return parent_qualified_names + (self.string_name,) + def __repr__(self): try: name = self.parent_context.name # __name__ is not defined all the time diff --git a/jedi/evaluate/gradual/conversion.py b/jedi/evaluate/gradual/conversion.py index 4109a70f..8d70012c 100644 --- a/jedi/evaluate/gradual/conversion.py +++ b/jedi/evaluate/gradual/conversion.py @@ -104,8 +104,36 @@ def _load_stub_module(module): ) -def name_to_stub(name): - return ContextSet.from_sets(to_stub(c) for c in name.infer()) +@to_list +def actual_to_stub_names(names, fallback_to_actual=False): + for name in names: + module = name.get_root_context() + if module.is_stub(): + yield name + continue + + name_list = name.get_qualified_names() + stubs = NO_CONTEXTS + if name_list is not None: + stub_module = _load_stub_module(module) + if stub_module is not None: + stubs = ContextSet({stub_module}) + for name in name_list[:-1]: + stubs = stubs.py__getattribute__(name) + if stubs and name_list: + new_names = stubs.py__getattribute__(name_list[-1], is_goto=True) + for new_name in new_names: + yield new_name + if new_names: + continue + elif stubs: + for c in stubs: + yield c.name + continue + if fallback_to_actual: + # This is the part where if we haven't found anything, just return + # the stub name. + yield name def to_stub(context): diff --git a/jedi/evaluate/names.py b/jedi/evaluate/names.py index 0392f3e2..185abad6 100644 --- a/jedi/evaluate/names.py +++ b/jedi/evaluate/names.py @@ -27,8 +27,18 @@ class AbstractNameDefinition(object): # name will always result on itself. return {self} - @abstractmethod def get_qualified_names(self, include_module_names=False): + qualified_names = self._get_qualified_names() + if qualified_names is None or not include_module_names: + return qualified_names + + module_names = self.get_root_context().string_names + if module_names is None: + return None + return module_names + qualified_names + + @abstractmethod + def _get_qualified_names(self): raise NotImplementedError def get_root_context(self): @@ -56,21 +66,20 @@ class AbstractTreeName(AbstractNameDefinition): def get_qualified_names(self, include_module_names=False): import_node = search_ancestor(self.tree_name, 'import_name', 'import_from') if import_node is not None: - return tuple(n.value for n in import_node.get_path_for_name(self.tree_name)) + if include_module_names: + return tuple(n.value for n in import_node.get_path_for_name(self.tree_name)) + else: + return () + return super(AbstractTreeName, self).get_qualified_names(include_module_names) + def _get_qualified_names(self): parent_names = self.parent_context.get_qualified_names() if parent_names is None: return None - parent_names += (self.tree_name.value,) - if include_module_names: - module_names = self.get_root_context().string_names - if module_names is None: - return None - return module_names + parent_names - return parent_names + return parent_names + (self.tree_name.value,) - def goto(self): - return self.parent_context.evaluator.goto(self.parent_context, self.tree_name) + def goto(self, **kwargs): + return self.parent_context.evaluator.goto(self.parent_context, self.tree_name, **kwargs) def is_import(self): imp = search_ancestor(self.tree_name, 'import_from', 'import_name') @@ -89,15 +98,8 @@ class ContextNameMixin(object): def infer(self): return ContextSet([self._context]) - def get_qualified_names(self, include_module_names=False): - qualified_names = self._context.get_qualified_names() - if qualified_names is None or not include_module_names: - return qualified_names - - module_names = self.get_root_context().string_names - if module_names is None: - return None - return module_names + qualified_names + def _get_qualified_names(self): + return self._context.get_qualified_names() def get_root_context(self): if self.parent_context is None: # A module @@ -115,8 +117,7 @@ class ContextName(ContextNameMixin, AbstractTreeName): self._context = context def goto(self): - from jedi.evaluate.gradual.conversion import try_stub_to_actual_names - return try_stub_to_actual_names([self._context.name]) + return ContextSet([self._context.name]) class TreeNameDefinition(AbstractTreeName): diff --git a/test/test_api/test_keyword.py b/test/test_api/test_keyword.py index 6321d06e..219e3ff8 100644 --- a/test/test_api/test_keyword.py +++ b/test/test_api/test_keyword.py @@ -35,7 +35,7 @@ def test_keyword_attributes(Script): assert def_.complete == '' assert def_.is_keyword is True assert def_.is_stub() is False - assert def_.goto_stubs() == [] + assert def_.goto_assignments(only_stubs=True) == [] assert def_.goto_assignments() == [] assert def_.infer() == [] assert def_.parent() is None diff --git a/test/test_evaluate/test_gradual/test_typeshed.py b/test/test_evaluate/test_gradual/test_typeshed.py index 8dc5fc0f..4cd485a8 100644 --- a/test/test_evaluate/test_gradual/test_typeshed.py +++ b/test/test_evaluate/test_gradual/test_typeshed.py @@ -63,7 +63,7 @@ def test_keywords_variable(Script): for seq in Script(code).goto_definitions(): assert seq.name == 'Sequence' # This points towards the typeshed implementation - stub_seq, = seq.goto_stubs() + stub_seq, = seq.goto_assignments(only_stubs=True) assert typeshed.TYPESHED_PATH in stub_seq.module_path @@ -156,12 +156,12 @@ def test_math_is_stub(Script, code, full_name): wanted = os.path.join('typeshed', 'stdlib', '2and3', 'math.pyi') assert cos.module_path.endswith(wanted) assert cos.is_stub() is True - assert cos.goto_stubs() == [cos] + assert cos.goto_assignments(only_stubs=True) == [cos] assert cos.full_name == full_name cos, = s.goto_assignments() assert cos.module_path.endswith(wanted) - assert cos.goto_stubs() == [cos] + assert cos.goto_assignments(only_stubs=True) == [cos] assert cos.is_stub() is True assert cos.full_name == full_name @@ -171,7 +171,7 @@ def test_goto_stubs(Script): os_module, = s.goto_definitions() assert os_module.full_name == 'os' assert os_module.is_stub() is False - stub, = os_module.goto_stubs() + stub, = os_module.goto_assignments(only_stubs=True) assert stub.is_stub() is True os_module, = s.goto_assignments() @@ -202,7 +202,7 @@ def test_goto_stubs_on_itself(Script, code, type_): def_, = s.goto_definitions() else: def_, = s.goto_assignments(follow_imports=True) - stub, = def_.goto_stubs() + stub, = def_.goto_assignments(only_stubs=True) script_on_source = Script( path=def_.module_path, @@ -213,7 +213,7 @@ def test_goto_stubs_on_itself(Script, code, type_): definition, = script_on_source.goto_definitions() else: definition, = script_on_source.goto_assignments() - same_stub, = definition.goto_stubs() + same_stub, = definition.goto_assignments(only_stubs=True) _assert_is_same(same_stub, stub) _assert_is_same(definition, def_) assert same_stub.module_path != def_.module_path