mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-08 14:54:47 +08:00
Avoid evaluating properties just for the api type, improves #1933
This commit is contained in:
@@ -142,9 +142,9 @@ class MixedObjectFilter(compiled.CompiledValueFilter):
|
|||||||
super().__init__(inference_state, compiled_value)
|
super().__init__(inference_state, compiled_value)
|
||||||
self._tree_value = tree_value
|
self._tree_value = tree_value
|
||||||
|
|
||||||
def _create_name(self, name):
|
def _create_name(self, *args, **kwargs):
|
||||||
return MixedName(
|
return MixedName(
|
||||||
super()._create_name(name),
|
super()._create_name(*args, **kwargs),
|
||||||
self._tree_value,
|
self._tree_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -311,11 +311,12 @@ class CompiledModule(CompiledValue):
|
|||||||
|
|
||||||
|
|
||||||
class CompiledName(AbstractNameDefinition):
|
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._inference_state = inference_state
|
||||||
self.parent_context = parent_value.as_context()
|
self.parent_context = parent_value.as_context()
|
||||||
self._parent_value = parent_value
|
self._parent_value = parent_value
|
||||||
self.string_name = name
|
self.string_name = name
|
||||||
|
self.is_descriptor = is_descriptor
|
||||||
|
|
||||||
def py__doc__(self):
|
def py__doc__(self):
|
||||||
return self.infer_compiled_value().py__doc__()
|
return self.infer_compiled_value().py__doc__()
|
||||||
@@ -342,6 +343,11 @@ class CompiledName(AbstractNameDefinition):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def api_type(self):
|
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
|
return self.infer_compiled_value().api_type
|
||||||
|
|
||||||
def infer(self):
|
def infer(self):
|
||||||
@@ -456,14 +462,14 @@ class CompiledValueFilter(AbstractFilter):
|
|||||||
|
|
||||||
if self.is_instance and not in_dir_callback(name):
|
if self.is_instance and not in_dir_callback(name):
|
||||||
return []
|
return []
|
||||||
return [self._get_cached_name(name)]
|
return [self._get_cached_name(name, is_descriptor=is_descriptor)]
|
||||||
|
|
||||||
@memoize_method
|
@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:
|
if is_empty:
|
||||||
return EmptyCompiledName(self._inference_state, name)
|
return EmptyCompiledName(self._inference_state, name)
|
||||||
else:
|
else:
|
||||||
return self._create_name(name)
|
return self._create_name(name, is_descriptor=is_descriptor)
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
from jedi.inference.compiled import builtin_from_name
|
from jedi.inference.compiled import builtin_from_name
|
||||||
@@ -487,11 +493,12 @@ class CompiledValueFilter(AbstractFilter):
|
|||||||
names += filter.values()
|
names += filter.values()
|
||||||
return names
|
return names
|
||||||
|
|
||||||
def _create_name(self, name):
|
def _create_name(self, name, is_descriptor):
|
||||||
return CompiledName(
|
return CompiledName(
|
||||||
self._inference_state,
|
self._inference_state,
|
||||||
self.compiled_value,
|
self.compiled_value,
|
||||||
name
|
name,
|
||||||
|
is_descriptor,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|||||||
@@ -102,11 +102,13 @@ def test_side_effect_completion():
|
|||||||
assert foo.name == 'foo'
|
assert foo.name == 'foo'
|
||||||
|
|
||||||
|
|
||||||
def _assert_interpreter_complete(source, namespace, completions,
|
def _assert_interpreter_complete(source, namespace, completions, *, check_type=False, **kwds):
|
||||||
**kwds):
|
|
||||||
script = jedi.Interpreter(source, [namespace], **kwds)
|
script = jedi.Interpreter(source, [namespace], **kwds)
|
||||||
cs = script.complete()
|
cs = script.complete()
|
||||||
actual = [c.name for c in cs]
|
actual = [c.name for c in cs]
|
||||||
|
if check_type:
|
||||||
|
for c in cs:
|
||||||
|
c.type
|
||||||
assert sorted(actual) == sorted(completions)
|
assert sorted(actual) == sorted(completions)
|
||||||
|
|
||||||
|
|
||||||
@@ -757,3 +759,23 @@ def test_keyword_param_completion(code, expected):
|
|||||||
import random
|
import random
|
||||||
completions = jedi.Interpreter(code, [locals()]).complete()
|
completions = jedi.Interpreter(code, [locals()]).complete()
|
||||||
assert expected == [c.name for c in completions if c.name.endswith('=')]
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user