diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index e3f0259f..f691707d 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -9,6 +9,7 @@ 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.syntax_tree import eval_atom from jedi.evaluate.helpers import evaluate_call_of_leaf from jedi.evaluate.compiled import get_string_context_set @@ -146,15 +147,22 @@ def evaluate_goto_definition(evaluator, context, leaf, prefer_stubs=False): return evaluator.goto_definitions(context, leaf) parent = leaf.parent + definitions = NO_CONTEXTS if parent.type == 'atom': - return context.eval_node(leaf.parent) + definitions = context.eval_node(leaf.parent) elif parent.type == 'trailer': - return evaluate_call_of_leaf(context, leaf) + definitions = evaluate_call_of_leaf(context, leaf) elif isinstance(leaf, tree.Literal): return eval_atom(context, leaf) elif leaf.type in ('fstring_string', 'fstring_start', 'fstring_end'): return get_string_context_set(evaluator) - return [] + if prefer_stubs: + return definitions + from jedi.evaluate.gradual.conversion import try_stubs_to_actual_context_set + return try_stubs_to_actual_context_set( + definitions, + prefer_stub_to_compiled=True, + ) CallSignatureDetails = namedtuple( diff --git a/jedi/evaluate/gradual/conversion.py b/jedi/evaluate/gradual/conversion.py index a04a6003..450abc09 100644 --- a/jedi/evaluate/gradual/conversion.py +++ b/jedi/evaluate/gradual/conversion.py @@ -9,8 +9,19 @@ def stub_to_actual_context_set(stub_context, ignore_compiled=False): if not stub_module.is_stub(): return ContextSet([stub_context]) + was_instance = stub_context.is_instance() + if was_instance: + stub_context = stub_context.py__class__() + qualified_names = stub_context.get_qualified_names() - return _infer_from_stub(stub_module, qualified_names, ignore_compiled) + contexts = _infer_from_stub(stub_module, qualified_names, ignore_compiled) + if was_instance: + contexts = ContextSet.from_sets( + c.execute_evaluated() + for c in contexts + if c.is_class() + ) + return contexts def _infer_from_stub(stub_module, qualified_names, ignore_compiled): @@ -83,6 +94,10 @@ def _to_stub(context): if context.is_stub(): return ContextSet([context]) + was_instance = context.is_instance() + if was_instance: + context = context.py__class__() + qualified_names = context.get_qualified_names() stub_module = _load_stub_module(context.get_root_context()) if stub_module is None or qualified_names is None: @@ -91,4 +106,11 @@ def _to_stub(context): stub_contexts = ContextSet([stub_module]) for name in qualified_names: stub_contexts = stub_contexts.py__getattribute__(name) + + if was_instance: + stub_contexts = ContextSet.from_sets( + c.execute_evaluated() + for c in stub_contexts + if c.is_class() + ) return stub_contexts diff --git a/test/test_evaluate/test_gradual/test_typeshed.py b/test/test_evaluate/test_gradual/test_typeshed.py index ca4b5c23..015971d2 100644 --- a/test/test_evaluate/test_gradual/test_typeshed.py +++ b/test/test_evaluate/test_gradual/test_typeshed.py @@ -60,10 +60,11 @@ def test_function(Script, environment): def test_keywords_variable(Script): code = 'import keyword; keyword.kwlist' - def_, = Script(code).goto_definitions() - assert def_.name == 'Sequence' + seq1, seq2 = Script(code).goto_definitions() + assert seq1.name, seq2.name == 'Sequence' # This points towards the typeshed implementation - assert typeshed.TYPESHED_PATH in def_.module_path + stub_seq, = seq1.goto_stubs() + assert typeshed.TYPESHED_PATH in stub_seq.module_path def test_class(Script): @@ -187,6 +188,7 @@ def _assert_is_same(d1, d2): 'code', [ 'import os; os.walk', 'from collections import Counter; Counter', + 'from collections import Counter; Counter()', 'from collections import Counter; Counter.most_common', ]) def test_goto_stubs_on_itself(Script, code, type_):