diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index c6c3c48a..dca61a68 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -40,7 +40,7 @@ from jedi.evaluate.base_context import ContextSet from jedi.evaluate.context.iterable import unpack_tuple_to_dict #from jedi.evaluate.gradual.typeshed import try_to_merge_with_stub from jedi.evaluate.gradual.stub_context import try_stubs_to_actual_context_set, \ - try_stubs_to_actual_names + try_stub_to_actual_names from jedi.evaluate.gradual.utils import load_proper_stub_module # Jedi uses lots and lots of recursion. By setting this a little bit higher, we @@ -308,7 +308,7 @@ class Script(object): return isinstance(name, imports.SubModuleName) names = filter_follow_imports(names, check) - names = try_stubs_to_actual_names(names, prefer_stub_to_compiled=True) + names = try_stub_to_actual_names(names, prefer_stub_to_compiled=True) defs = [classes.Definition(self._evaluator, d) for d in set(names)] return helpers.sorted_definitions(defs) diff --git a/jedi/evaluate/base_context.py b/jedi/evaluate/base_context.py index ee6cae36..018f42b0 100644 --- a/jedi/evaluate/base_context.py +++ b/jedi/evaluate/base_context.py @@ -101,6 +101,10 @@ class HelperContextMixin(object): return class2.is_same_class(self) return self == class2 + def is_stub(self): + # The root context knows if it's a stub or not. + return self.parent_context.is_stub() + class Context(HelperContextMixin, BaseContext): """ @@ -154,9 +158,6 @@ class Context(HelperContextMixin, BaseContext): def is_module(self): return False - def is_stub(self): - return False - def is_compiled(self): return False diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index 4ce4b0b2..6d0b622b 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -102,6 +102,9 @@ class CompiledObject(Context): def is_compiled(self): return True + def is_stub(self): + return False + def py__doc__(self, include_call_signature=False): return self.access_handle.py__doc__() diff --git a/jedi/evaluate/context/instance.py b/jedi/evaluate/context/instance.py index d135facc..0f959d7b 100644 --- a/jedi/evaluate/context/instance.py +++ b/jedi/evaluate/context/instance.py @@ -393,12 +393,6 @@ class BoundMethod(FunctionMixin, ContextWrapper): if isinstance(self._wrapped_context, OverloadedFunctionContext): return self._wrapped_context.py__call__(self._get_arguments(arguments)) - # This might not be the most beautiful way, but prefer stub_contexts - # and execute those if possible. - stub_context = self._wrapped_context.stub_context - if stub_context is not None: - return stub_context.py__call__(arguments) - function_execution = self.get_function_execution(arguments) return function_execution.infer() diff --git a/jedi/evaluate/context/module.py b/jedi/evaluate/context/module.py index d432d15a..b0b8f366 100644 --- a/jedi/evaluate/context/module.py +++ b/jedi/evaluate/context/module.py @@ -93,6 +93,9 @@ class ModuleMixin(SubModuleDictMixin): def is_module(self): return True + def is_stub(self): + return False + @property @evaluator_method_cache() def name(self): diff --git a/jedi/evaluate/gradual/stub_context.py b/jedi/evaluate/gradual/stub_context.py index 4b16dbcc..5baff0ea 100644 --- a/jedi/evaluate/gradual/stub_context.py +++ b/jedi/evaluate/gradual/stub_context.py @@ -298,6 +298,8 @@ def with_stub_context_if_possible(actual_context): def goto_with_stubs_if_possible(name): + return [name] + # XXX root = name.parent_context.get_root_context() stub = root.get_root_context().stub_context if stub is None: @@ -323,7 +325,7 @@ def goto_non_stub(parent_context, tree_name): return contexts.py__getattribute__(tree_name, is_goto=True) -def stub_to_actual_context_set(stub_context): +def stub_to_actual_context_set(stub_context, ignore_compiled=False): qualified_names = stub_context.get_qualified_names() if qualified_names is None: return NO_CONTEXTS @@ -335,6 +337,8 @@ def stub_to_actual_context_set(stub_context): assert isinstance(stub_module, StubOnlyModuleContext), stub_module non_stubs = stub_module.non_stub_context_set + if ignore_compiled: + non_stubs = non_stubs.filter(lambda c: not c.is_compiled()) for name in qualified_names: non_stubs = non_stubs.py__getattribute__(name) return non_stubs @@ -342,34 +346,47 @@ def stub_to_actual_context_set(stub_context): def try_stubs_to_actual_context_set(stub_contexts, prefer_stub_to_compiled=False): return ContextSet.from_sets( - stub_to_actual_context_set(stub_context) or ContextSet(stub_context) + stub_to_actual_context_set(stub_context, ignore_compiled=prefer_stub_to_compiled) + or ContextSet([stub_context]) for stub_context in stub_contexts ) @to_list -def try_stubs_to_actual_names(names, prefer_stub_to_compiled=False): +def try_stub_to_actual_names(names, prefer_stub_to_compiled=False): for name in names: - parent_context = name.parent_context + # Using the tree_name is better, if it's available, becuase no + # information is lost. If the name given is defineda as `foo: int` we + # would otherwise land on int, which is not what we want. We want foo + # from the non-stub module. if name.tree_name is None: - continue - - if not parent_context.get_root_context().is_stub(): - yield name - continue - - contexts = stub_to_actual_context_set(parent_context) - if prefer_stub_to_compiled: - # We don't really care about - contexts = ContextSet([c for c in contexts if not c.is_compiled()]) - - new_names = contexts.py__getattribute__(name.tree_name, is_goto=True) - - if new_names: - for n in new_names: - yield n + actual_contexts = ContextSet.from_sets( + stub_to_actual_context_set(c) for c in name.infer() + ) + actual_contexts = actual_contexts.filter(lambda c: not c.is_compiled()) + if actual_contexts: + for s in actual_contexts: + yield s.name + else: + yield name else: - yield name + parent_context = name.parent_context + if not parent_context.is_stub(): + yield name + continue + + contexts = stub_to_actual_context_set(parent_context) + if prefer_stub_to_compiled: + # We don't really care about + contexts = contexts.filter(lambda c: not c.is_compiled()) + + new_names = contexts.py__getattribute__(name.tree_name, is_goto=True) + + if new_names: + for n in new_names: + yield n + else: + yield name def stubify(parent_context, context): diff --git a/test/test_evaluate/test_gradual/test_typeshed.py b/test/test_evaluate/test_gradual/test_typeshed.py index 76b816f4..9a780fc3 100644 --- a/test/test_evaluate/test_gradual/test_typeshed.py +++ b/test/test_evaluate/test_gradual/test_typeshed.py @@ -4,7 +4,8 @@ import pytest from parso.utils import PythonVersionInfo from jedi.evaluate.gradual import typeshed, stub_context -from jedi.evaluate.context import TreeInstance, BoundMethod, FunctionContext +from jedi.evaluate.context import TreeInstance, BoundMethod, FunctionContext, \ + MethodContext, ClassContext from jedi.evaluate.filters import TreeNameDefinition TYPESHED_PYTHON3 = os.path.join(typeshed.TYPESHED_PATH, 'stdlib', '3') @@ -45,17 +46,10 @@ def test_get_stub_files(): def test_function(Script, environment): - if environment.version_info.major == 2: - # In Python 2, the definitions are a bit weird in typeshed. Therefore - # it's for now a FunctionContext. - expected = FunctionContext - else: - expected = stub_context.StubFunctionContext - code = 'import threading; threading.current_thread' def_, = Script(code).goto_definitions() context = def_._name._context - assert isinstance(context, expected), context + assert isinstance(context, FunctionContext), context def_, = Script(code + '()').goto_definitions() context = def_._name._context @@ -63,7 +57,7 @@ def test_function(Script, environment): assert isinstance(context.class_context, stub_context.StubOnlyClass), context def_, = Script('import threading; threading.Thread').goto_definitions() - assert isinstance(def_._name._context, stub_context.StubClassContext), def_ + assert isinstance(def_._name._context, ClassContext), def_ def test_keywords_variable(Script): @@ -77,20 +71,20 @@ def test_keywords_variable(Script): def test_class(Script): def_, = Script('import threading; threading.Thread').goto_definitions() context = def_._name._context - assert isinstance(context, stub_context.StubClassContext), context + assert isinstance(context, ClassContext), context def test_instance(Script): def_, = Script('import threading; threading.Thread()').goto_definitions() context = def_._name._context assert isinstance(context, TreeInstance) - assert isinstance(context.class_context, stub_context.StubClassContext), context + assert isinstance(context.class_context, stub_context.StubOnlyClass), context def test_class_function(Script): def_, = Script('import threading; threading.Thread.getName').goto_definitions() context = def_._name._context - assert isinstance(context, stub_context.StubFunctionContext), context + assert isinstance(context, MethodContext), context def test_method(Script): @@ -98,7 +92,7 @@ def test_method(Script): def_, = Script(code).goto_definitions() context = def_._name._context assert isinstance(context, BoundMethod), context - assert isinstance(context._wrapped_context, stub_context.StubFunctionContext), context + assert isinstance(context._wrapped_context, MethodContext), context def_, = Script(code + '()').goto_definitions() context = def_._name._context @@ -132,8 +126,7 @@ def test_sys_getwindowsversion(Script, environment): def test_sys_hexversion(Script): script = Script('import sys; sys.hexversion') def_, = script.completions() - assert isinstance(def_._name, stub_context.CompiledStubName), def_._name - assert isinstance(def_._name._wrapped_name, TreeNameDefinition) + assert isinstance(def_._name, stub_context.StubOnlyName), def_._name assert typeshed.TYPESHED_PATH in def_.module_path def_, = script.goto_definitions() assert def_.name == 'int'