From ff3a7f367f9f0c56a866f0863d0d5abbfcfdbc65 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 28 Jul 2023 22:11:15 +0200 Subject: [PATCH] Avoid evaluating properties just for the api type, improves #1933 --- jedi/inference/compiled/mixed.py | 4 ++-- jedi/inference/compiled/value.py | 19 +++++++++++++------ test/test_api/test_interpreter.py | 26 ++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/jedi/inference/compiled/mixed.py b/jedi/inference/compiled/mixed.py index 9ad9b928..1bfa3405 100644 --- a/jedi/inference/compiled/mixed.py +++ b/jedi/inference/compiled/mixed.py @@ -142,9 +142,9 @@ class MixedObjectFilter(compiled.CompiledValueFilter): super().__init__(inference_state, compiled_value) self._tree_value = tree_value - def _create_name(self, name): + def _create_name(self, *args, **kwargs): return MixedName( - super()._create_name(name), + super()._create_name(*args, **kwargs), self._tree_value, ) diff --git a/jedi/inference/compiled/value.py b/jedi/inference/compiled/value.py index bc716922..d03f1743 100644 --- a/jedi/inference/compiled/value.py +++ b/jedi/inference/compiled/value.py @@ -311,11 +311,12 @@ class CompiledModule(CompiledValue): class CompiledName(AbstractNameDefinition): - def __init__(self, inference_state, parent_value, name): + def __init__(self, inference_state, parent_value, name, is_descriptor): self._inference_state = inference_state self.parent_context = parent_value.as_context() self._parent_value = parent_value self.string_name = name + self.is_descriptor = is_descriptor def py__doc__(self): return self.infer_compiled_value().py__doc__() @@ -342,6 +343,11 @@ class CompiledName(AbstractNameDefinition): @property def api_type(self): + if self.is_descriptor: + # In case of properties we want to avoid executions as much as + # possible. Since the api_type can be wrong for other reasons + # anyway, we just return instance here. + return "instance" return self.infer_compiled_value().api_type def infer(self): @@ -456,14 +462,14 @@ class CompiledValueFilter(AbstractFilter): if self.is_instance and not in_dir_callback(name): return [] - return [self._get_cached_name(name)] + return [self._get_cached_name(name, is_descriptor=is_descriptor)] @memoize_method - def _get_cached_name(self, name, is_empty=False): + def _get_cached_name(self, name, is_empty=False, *, is_descriptor=False): if is_empty: return EmptyCompiledName(self._inference_state, name) else: - return self._create_name(name) + return self._create_name(name, is_descriptor=is_descriptor) def values(self): from jedi.inference.compiled import builtin_from_name @@ -487,11 +493,12 @@ class CompiledValueFilter(AbstractFilter): names += filter.values() return names - def _create_name(self, name): + def _create_name(self, name, is_descriptor): return CompiledName( self._inference_state, self.compiled_value, - name + name, + is_descriptor, ) def __repr__(self): diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index f7647e2d..63392405 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -102,11 +102,13 @@ def test_side_effect_completion(): assert foo.name == 'foo' -def _assert_interpreter_complete(source, namespace, completions, - **kwds): +def _assert_interpreter_complete(source, namespace, completions, *, check_type=False, **kwds): script = jedi.Interpreter(source, [namespace], **kwds) cs = script.complete() actual = [c.name for c in cs] + if check_type: + for c in cs: + c.type assert sorted(actual) == sorted(completions) @@ -757,3 +759,23 @@ def test_keyword_param_completion(code, expected): import random completions = jedi.Interpreter(code, [locals()]).complete() assert expected == [c.name for c in completions if c.name.endswith('=')] + + +@pytest.mark.parametrize('class_is_findable', [False, True]) +def test_avoid_descriptor_executions_if_not_necessary(class_is_findable): + counter = 0 + class AvoidDescriptor(object): + @property + def prop(self): + nonlocal counter + counter += 1 + return self + + if not class_is_findable: + AvoidDescriptor.__name__ = "something_somewhere" + namespace = {'b': AvoidDescriptor()} + expected = ['prop'] + _assert_interpreter_complete('b.pro', namespace, expected, check_type=True) + assert counter == 0 + _assert_interpreter_complete('b.prop.pro', namespace, expected, check_type=True) + assert counter == 1