diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 81ef1f29..84a2685e 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -277,9 +277,7 @@ class BaseDefinition(object): return '.'.join(names) def is_stub(self): - if not self._name.is_context_name: - return False - return all(c.is_stub() for c in self._name.infer()) + return self._name.get_root_context().is_stub() def goto_assignments(self, **kwargs): # Python 2... with debug.increase_indent_cm('goto for %s' % self._name): diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index 4946a0d9..75024df1 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -9,11 +9,13 @@ from parso.python.parser import Parser from parso.python import tree from jedi._compatibility import u -from jedi.evaluate.base_context import NO_CONTEXTS +from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet from jedi.evaluate.syntax_tree import eval_atom from jedi.evaluate.helpers import evaluate_call_of_leaf from jedi.evaluate.compiled import get_string_context_set from jedi.cache import call_signature_time_cache +from jedi.evaluate.gradual.conversion import convert_names +from jedi.evaluate.names import TreeNameDefinition CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name']) @@ -141,15 +143,23 @@ def evaluate_goto_definition(evaluator, context, leaf): if leaf.type == 'name': # In case of a name we can just use goto_definition which does all the # magic itself. - return evaluator.goto_definitions(context, leaf) + name = TreeNameDefinition(context, leaf) + if leaf.parent.type in ('trailer', 'error_node'): + return evaluator.goto_definitions(context, leaf) + return ContextSet.from_sets( + name.infer() for name in convert_names([name], prefer_stubs=True) + ) parent = leaf.parent definitions = NO_CONTEXTS if parent.type == 'atom': + # e.g. `(a + b)` definitions = context.eval_node(leaf.parent) elif parent.type == 'trailer': + # e.g. `a()` definitions = evaluate_call_of_leaf(context, leaf) elif isinstance(leaf, tree.Literal): + # e.g. `"foo"` or `1.0` return eval_atom(context, leaf) elif leaf.type in ('fstring_string', 'fstring_start', 'fstring_end'): return get_string_context_set(evaluator) diff --git a/jedi/evaluate/gradual/conversion.py b/jedi/evaluate/gradual/conversion.py index ac0382a5..e89ec852 100644 --- a/jedi/evaluate/gradual/conversion.py +++ b/jedi/evaluate/gradual/conversion.py @@ -4,6 +4,10 @@ from jedi.evaluate.base_context import ContextSet, \ from jedi.evaluate.utils import to_list from jedi.evaluate.gradual.stub_context import StubModuleContext +_QUALIFIED_NAME_CHANGES = dict( + _collections="collections", +) + def _stub_to_python_context_set(stub_context, ignore_compiled=False): stub_module = stub_context.get_root_context() @@ -102,9 +106,19 @@ def _python_to_stub_names(names, fallback_to_python=False): yield name continue + if name.is_import(): + for new_name in name.goto(): + # Imports don't need to be converted, because they are already + # stubs if possible. + if fallback_to_python or new_name.is_stub(): + yield new_name + continue + print(name.goto()) + name_list = name.get_qualified_names() stubs = NO_CONTEXTS if name_list is not None: + name_list = _apply_qualified_name_changes(name_list) stub_module = _load_stub_module(module) if stub_module is not None: stubs = ContextSet({stub_module}) @@ -165,6 +179,7 @@ def to_stub(context): stub_module = _load_stub_module(context.get_root_context()) if stub_module is None or qualified_names is None: return NO_CONTEXTS + qualified_names = _apply_qualified_name_changes(qualified_names) was_bound_method = context.is_bound_method() if was_bound_method: @@ -188,3 +203,19 @@ def to_stub(context): # the method. stub_contexts = stub_contexts.py__getattribute__(method_name) return stub_contexts + + +def _apply_qualified_name_changes(qualified_names): + """ + Some x + """ + print(qualified_names) + try: + new_first_name = _QUALIFIED_NAME_CHANGES[qualified_names[0]] + except (KeyError, IndexError): + pass + else: + qualified_names = list(qualified_names) + qualified_names[0] = new_first_name + print(qualified_names) + return qualified_names diff --git a/jedi/evaluate/names.py b/jedi/evaluate/names.py index 2b525cad..dc41b829 100644 --- a/jedi/evaluate/names.py +++ b/jedi/evaluate/names.py @@ -75,7 +75,7 @@ class AbstractTreeName(AbstractNameDefinition): if include_module_names and not import_node.level: return tuple(n.value for n in import_node.get_path_for_name(self.tree_name)) else: - return () + return None return super(AbstractTreeName, self).get_qualified_names(include_module_names) diff --git a/test/test_evaluate/test_gradual/test_stubs.py b/test/test_evaluate/test_gradual/test_stubs.py index 4dd09ba3..4b7fddb5 100644 --- a/test/test_evaluate/test_gradual/test_stubs.py +++ b/test/test_evaluate/test_gradual/test_stubs.py @@ -24,6 +24,8 @@ from test.helpers import root_dir ['from collections import Counter; Counter()', 'collections.Counter', True, True, {}], ['from collections import Counter; Counter.most_common', 'collections.Counter.most_common', True, True, {}], + #['from collections import deque', + #('_collections.deque', 'collections.deque'), True, False, {}], ['from keyword import kwlist; kwlist', 'typing.Sequence', True, True, {'full_name': 'keyword.kwlist'}], @@ -41,6 +43,11 @@ def test_infer_and_goto(Script, code, full_name, has_stub, has_python, way, s = Script(code, _project=project) prefer_stubs = kwargs['prefer_stubs'] only_stubs = kwargs['only_stubs'] + if isinstance(full_name, tuple): + # Small hack to make it possible to test some special cases. + should_land_on_stub = has_stub and (only_stubs or prefer_stubs) + full_name = full_name[should_land_on_stub] + if type_ == 'goto': full_name = goto_changes.get('full_name', full_name) if way == 'direct': @@ -70,3 +77,5 @@ def test_infer_and_goto(Script, code, full_name, has_stub, has_python, way, if only_stubs: assert d.is_stub() assert d.full_name == full_name + #if d.is_stub(): + #assert d.module_path.endswith('.pyi')