diff --git a/jedi/api/classes.py b/jedi/api/classes.py index b44e5eb8..b7aaadd3 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -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): """ diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index 96f43395..05134773 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -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([]) diff --git a/jedi/inference/compiled/value.py b/jedi/inference/compiled/value.py index fe8c03c2..bad5c73f 100644 --- a/jedi/inference/compiled/value.py +++ b/jedi/inference/compiled/value.py @@ -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', ""): + return 'None' + return None + class CompiledModule(CompiledValue): file_io = None # For modules diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index e09766be..d3472bdb 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -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): """ diff --git a/jedi/inference/gradual/generics.py b/jedi/inference/gradual/generics.py index f0060e4f..6bd6aa63 100644 --- a/jedi/inference/gradual/generics.py +++ b/jedi/inference/gradual/generics.py @@ -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): diff --git a/jedi/inference/value/instance.py b/jedi/inference/value/instance.py index 858c778e..2eca9f9d 100644 --- a/jedi/inference/value/instance.py +++ b/jedi/inference/value/instance.py @@ -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) diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index b7d79646..37b743b3 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -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' diff --git a/test/test_api/test_classes.py b/test/test_api/test_classes.py index 4a17c572..b27c30e6 100644 --- a/test/test_api/test_classes.py +++ b/test/test_api/test_classes.py @@ -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