1
0
forked from VimPlug/jedi

Generate type hints, fixes #987

This commit is contained in:
Dave Halter
2020-02-02 16:55:10 +01:00
parent f4b1fc479d
commit 4c7179bc87
8 changed files with 91 additions and 0 deletions

View File

@@ -513,6 +513,16 @@ class BaseDefinition(object):
def execute(self):
return _values_to_definitions(self._name.infer().execute_with_values())
def get_type_hint(self):
"""
Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
This method might be quite slow, especially for functions. The problem
is finding executions for those functions to return something like
``Callable[[int, str], str]``.
"""
return self._name.infer().get_type_hint()
class Completion(BaseDefinition):
"""

View File

@@ -264,6 +264,9 @@ class Value(HelperValueMixin, BaseValue):
def py__name__(self):
return self.name.string_name
def get_type_hint(self, add_class_info=True):
return None
def iterate_values(values, contextualized_node=None, is_async=False):
"""
@@ -414,6 +417,26 @@ class ValueSet(BaseValueSet):
def get_signatures(self):
return [sig for c in self._set for sig in c.get_signatures()]
def get_type_hint(self, add_class_info=True):
t = [v.get_type_hint(add_class_info=add_class_info) for v in self._set]
type_hints = sorted(filter(None, t))
if len(type_hints) == 1:
return type_hints[0]
optional = 'None' in type_hints
if optional:
type_hints.remove('None')
if len(type_hints) == 0:
return None
elif len(type_hints) == 1:
s = type_hints[0]
else:
s = 'Union[%s]' % ', '.join(type_hints)
if optional:
s = 'Optional[%s]' % s
return s
NO_VALUES = ValueSet([])

View File

@@ -285,6 +285,11 @@ class CompiledValue(Value):
for k in self.access_handle.get_key_paths()
]
def get_type_hint(self, add_class_info=True):
if self.access_handle.get_repr() in ('None', "<class 'NoneType'>"):
return 'None'
return None
class CompiledModule(CompiledValue):
file_io = None # For modules

View File

@@ -165,6 +165,14 @@ class GenericClass(ClassMixin, DefineGenericBase):
def _get_wrapped_value(self):
return self._class_value
def get_type_hint(self, add_class_info=True):
n = self.py__name__()
n = dict(list="List", dict="Dict", set="Set", tuple="Tuple").get(n, n)
s = n + self._generics_manager.get_type_hint()
if add_class_info:
return 'Type[%s]' % s
return s
def get_type_var_filter(self):
return _TypeVarFilter(self.get_generics(), self.list_type_vars())
@@ -239,6 +247,9 @@ class _GenericInstanceWrapper(ValueWrapper):
return ValueSet([builtin_from_name(self.inference_state, u'None')])
return self._wrapped_value.py__stop_iteration_returns()
def get_type_hint(self, add_class_info=True):
return self._wrapped_value.class_value.get_type_hint(add_class_info=False)
class _PseudoTreeNameClass(Value):
"""

View File

@@ -31,6 +31,9 @@ class _AbstractGenericManager(object):
debug.warning('No param #%s found for annotation %s', index, self)
return NO_VALUES
def get_type_hint(self):
return '[%s]' % ', '.join(t.get_type_hint(add_class_info=False) for t in self.to_tuple())
class LazyGenericManager(_AbstractGenericManager):
def __init__(self, context_of_index, index_value):

View File

@@ -130,6 +130,9 @@ class AbstractInstanceValue(Value):
for name in names
)
def get_type_hint(self, add_class_info=True):
return self.py__name__()
def __repr__(self):
return "<%s of %s>" % (self.__class__.__name__, self.class_value)

View File

@@ -221,6 +221,11 @@ class ClassMixin(object):
def _as_context(self):
return ClassContext(self)
def get_type_hint(self, add_class_info=True):
if add_class_info:
return 'Type[%s]' % self.py__name__()
return self.py__name__()
class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)):
api_type = u'class'

View File

@@ -558,3 +558,34 @@ def test_definition_goto_follow_imports(Script):
assert follow.description == 'def dumps'
assert follow.line != 1
assert follow.module_name == 'json'
@pytest.mark.parametrize(
'code, expected', [
('1', 'int'),
('x = None; x', 'None'),
('n: Optional[str]; n', 'Optional[str]'),
('n = None if xxxxx else ""; n', 'Optional[str]'),
('n = None if xxxxx else str(); n', 'Optional[str]'),
('n = None if xxxxx else str; n', 'Optional[Type[str]]'),
('class Foo: pass\nFoo', 'Type[Foo]'),
('class Foo: pass\nFoo()', 'Foo'),
('n: Type[List[int]]; n', 'Type[List[int]]'),
('n: Type[List]; n', 'Type[list]'),
('n: List[int]; n', 'List[int]'),
('n: Iterable[int]; n', 'Iterable[int]'),
('n = [1]; n', 'List[int]'),
('n = [1, ""]; n', 'List[Union[int, str]]'),
('n = [1, str(), None]; n', 'List[Optional[Union[int, str]]]'),
('n = {1, str()}; n', 'Set[Union[int, str]]'),
('n = (1,); n', 'Tuple[int]'),
('n = {1: ""}; n', 'Dict[int, str]'),
('n = {1: "", 1.0: b""}; n', 'Dict[Union[float, int], Union[bytes, str]]'),
]
)
def test_get_type_hint(Script, code, expected, skip_python2):
code = 'from typing import *\n' + code
d, = Script(code).goto()
assert d.get_type_hint() == expected