forked from VimPlug/jedi
Try to use the return annotations of properties, if available, fixes #1933
This commit is contained in:
@@ -354,7 +354,7 @@ class DirectObjectAccess:
|
|||||||
if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS:
|
if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS:
|
||||||
if isinstance(attr, property):
|
if isinstance(attr, property):
|
||||||
if hasattr(attr.fget, '__annotations__'):
|
if hasattr(attr.fget, '__annotations__'):
|
||||||
a = DirectObjectAccess(self._inference_state, attr)
|
a = DirectObjectAccess(self._inference_state, attr.fget)
|
||||||
return True, True, a.get_return_annotation()
|
return True, True, a.get_return_annotation()
|
||||||
# In case of descriptors that have get methods we cannot return
|
# In case of descriptors that have get methods we cannot return
|
||||||
# it's value, because that would mean code execution.
|
# it's value, because that would mean code execution.
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ class CompiledValue(Value):
|
|||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
return_annotation = self.access_handle.get_return_annotation()
|
return_annotation = self.access_handle.get_return_annotation()
|
||||||
if return_annotation is not None:
|
if return_annotation is not None:
|
||||||
# TODO the return annotation may also be a string.
|
|
||||||
return create_from_access_path(
|
return create_from_access_path(
|
||||||
self.inference_state,
|
self.inference_state,
|
||||||
return_annotation
|
return_annotation
|
||||||
@@ -453,6 +452,14 @@ class CompiledValueFilter(AbstractFilter):
|
|||||||
has_attribute, is_descriptor, property_return_annotation = allowed_getattr_callback(
|
has_attribute, is_descriptor, property_return_annotation = allowed_getattr_callback(
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
|
if property_return_annotation is not None:
|
||||||
|
values = create_from_access_path(
|
||||||
|
self._inference_state,
|
||||||
|
property_return_annotation
|
||||||
|
).execute_annotation()
|
||||||
|
if values:
|
||||||
|
return [CompiledValueName(v, name) for v in values]
|
||||||
|
|
||||||
if check_has_attribute and not has_attribute:
|
if check_has_attribute and not has_attribute:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
@@ -764,6 +764,7 @@ def test_keyword_param_completion(code, expected):
|
|||||||
@pytest.mark.parametrize('class_is_findable', [False, True])
|
@pytest.mark.parametrize('class_is_findable', [False, True])
|
||||||
def test_avoid_descriptor_executions_if_not_necessary(class_is_findable):
|
def test_avoid_descriptor_executions_if_not_necessary(class_is_findable):
|
||||||
counter = 0
|
counter = 0
|
||||||
|
|
||||||
class AvoidDescriptor(object):
|
class AvoidDescriptor(object):
|
||||||
@property
|
@property
|
||||||
def prop(self):
|
def prop(self):
|
||||||
@@ -779,3 +780,53 @@ def test_avoid_descriptor_executions_if_not_necessary(class_is_findable):
|
|||||||
assert counter == 0
|
assert counter == 0
|
||||||
_assert_interpreter_complete('b.prop.pro', namespace, expected, check_type=True)
|
_assert_interpreter_complete('b.prop.pro', namespace, expected, check_type=True)
|
||||||
assert counter == 1
|
assert counter == 1
|
||||||
|
|
||||||
|
|
||||||
|
class Hello:
|
||||||
|
its_me = 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('class_is_findable', [False, True])
|
||||||
|
def test_try_to_use_return_annotation_for_property(class_is_findable):
|
||||||
|
class WithProperties(object):
|
||||||
|
@property
|
||||||
|
def with_annotation1(self) -> str:
|
||||||
|
raise BaseException
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_annotation2(self) -> 'str':
|
||||||
|
raise BaseException
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_annotation3(self) -> Hello:
|
||||||
|
raise BaseException
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_annotation4(self) -> 'Hello':
|
||||||
|
raise BaseException
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_annotation_garbage1(self) -> 'asldjflksjdfljdslkjfsl': # noqa
|
||||||
|
return Hello()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_annotation_garbage2(self) -> 'sdf$@@$5*+8': # noqa
|
||||||
|
return Hello()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def without_annotation(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if not class_is_findable:
|
||||||
|
WithProperties.__name__ = "something_somewhere"
|
||||||
|
Hello.__name__ = "something_somewhere_else"
|
||||||
|
|
||||||
|
namespace = {'p': WithProperties()}
|
||||||
|
_assert_interpreter_complete('p.without_annotation.upp', namespace, ['upper'])
|
||||||
|
_assert_interpreter_complete('p.with_annotation1.upp', namespace, ['upper'])
|
||||||
|
_assert_interpreter_complete('p.with_annotation2.upp', namespace, ['upper'])
|
||||||
|
_assert_interpreter_complete('p.with_annotation3.its', namespace, ['its_me'])
|
||||||
|
_assert_interpreter_complete('p.with_annotation4.its', namespace, ['its_me'])
|
||||||
|
# This is a fallback, if the annotations don't help
|
||||||
|
_assert_interpreter_complete('p.with_annotation_garbage1.its', namespace, ['its_me'])
|
||||||
|
_assert_interpreter_complete('p.with_annotation_garbage2.its', namespace, ['its_me'])
|
||||||
|
|||||||
Reference in New Issue
Block a user