diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index dfca7d9c..b557d450 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -5,6 +5,7 @@ import sys import operator as op from collections import namedtuple import warnings +import re from jedi._compatibility import unicode, is_py3, builtins, \ py_version, force_unicode @@ -491,10 +492,15 @@ class DirectObjectAccess(object): if sys.version_info < (3, 5): return None, () - import typing - args = typing.get_args(self._obj) - origin = typing.get_origin(self._obj) - name = None if origin is None else str(origin) + name = None + args = () + if safe_getattr(self._obj, '__module__', default='') == 'typing': + m = re.match(r'typing.(\w+)\[', repr(self._obj)) + if m is not None: + name = m.group(1) + + import typing + args = typing.get_args(self._obj) return name, tuple(self._create_access_path(arg) for arg in args) def needs_type_completions(self): diff --git a/jedi/inference/compiled/value.py b/jedi/inference/compiled/value.py index 54814294..a98cd5cf 100644 --- a/jedi/inference/compiled/value.py +++ b/jedi/inference/compiled/value.py @@ -273,9 +273,20 @@ class CompiledObject(Value): return ValueSet([self]) name, args = self.access_handle.get_annotation_name_and_args() - arguments = [create_from_access_path(self.inference_state, path) for path in args] - if name == 'typing.Union': + arguments = [ + ValueSet([create_from_access_path(self.inference_state, path)]) + for path in args + ] + if name == 'Union': return ValueSet.from_sets(arg.execute_annotation() for arg in arguments) + elif name: + # While with_generics only exists on very specific objects, we + # should probably be fine, because we control all the typing + # objects. + return ValueSet([ + v.with_generics(arguments) + for v in self.inference_state.typing_module.py__getattribute__(name) + ]).execute_annotation() return super(CompiledObject, self).execute_annotation() def negate(self): diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 861cc9f0..cfdce3ea 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -16,7 +16,7 @@ from jedi.inference.names import NameWrapper, ValueName from jedi.inference.value.klass import ClassMixin from jedi.inference.gradual.base import BaseTypingValue, BaseTypingValueWithGenerics from jedi.inference.gradual.type_var import TypeVarClass -from jedi.inference.gradual.generics import LazyGenericManager +from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager _PROXY_CLASS_TYPES = 'Tuple Generic Protocol Callable Type'.split() _TYPE_ALIAS_TYPES = { @@ -144,6 +144,14 @@ class ProxyTypingValue(BaseTypingValue): index_class = TypingValueWithIndex py__simple_getitem__ = None + def with_generics(self, generics_tuple): + return self.index_class.create_cached( + self.inference_state, + self.parent_context, + self._tree_name, + generics_manager=TupleGenericManager(generics_tuple) + ) + def py__getitem__(self, index_value_set, contextualized_node): return ValueSet( self.index_class.create_cached( diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index aede4222..7932f995 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -286,6 +286,13 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase for index_value in index_value_set ) + def with_generics(self, generics_tuple): + from jedi.inference.gradual.base import GenericClass + return GenericClass( + self, + TupleGenericManager(generics_tuple) + ) + def define_generics(self, type_var_dict): from jedi.inference.gradual.base import GenericClass diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 639a0e64..821ea2d6 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -643,29 +643,46 @@ def bar(): @pytest.mark.skipif(sys.version_info < (3, 5), reason="Ignore Python 2, because EOL") @pytest.mark.parametrize( - 'annotations, result', [ - ({}, []), - (None, []), - ({'asdf': 'str'}, []), + 'annotations, result, code', [ + ({}, [], ''), + (None, [], ''), + ({'asdf': 'str'}, [], ''), + + ({'return': 'str'}, ['str'], ''), + ({'return': 'None'}, ['NoneType'], ''), + ({'return': 'str().upper'}, [], ''), + ({'return': 'foo()'}, [], ''), + ({'return': 'bar()'}, ['float'], ''), - ({'return': 'str'}, ['str']), - ({'return': 'str().upper'}, []), - ({'return': 'foo()'}, []), - ({'return': 'bar()'}, ['float']), # typing is available via globals. - ({'return': 'typing.Union[str, int]'}, ['int', 'str']), - ({'return': 'typing.Union["str", int]'}, ['int']), - ({'return': 'typing.Union["str", 1]'}, []), - ({'return': 'typing.Optional[str]'}, ['NoneType', 'str']), - ({'return': 'typing.Optional[str, int]'}, []), # Takes only one arg + ({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''), + ({'return': 'typing.Union["str", int]'}, ['int'], ''), + ({'return': 'typing.Union["str", 1]'}, [], ''), + ({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''), + ({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg + ({'return': 'typing.Any'}, [], ''), - ({'return': 'decimal.Decimal'}, []), - ({'return': 'lalalalallalaa'}, []), - ({'return': 'lalalalallalaa.lala'}, []), + ({'return': 'typing.Tuple[int, str]'}, ['tuple'], ''), + ({'return': 'typing.Tuple[int, str]'}, ['int'], 'x()[0]'), + ({'return': 'typing.Tuple[int, str]'}, ['str'], 'x()[1]'), + ({'return': 'typing.Tuple[int, str]'}, [], 'x()[2]'), + + ({'return': 'typing.List'}, ['list'], 'list'), + ({'return': 'typing.List[int]'}, ['list'], 'list'), + ({'return': 'typing.List[int]'}, ['int'], 'x()[0]'), + ({'return': 'typing.List[int, str]'}, [], 'x()[0]'), + + ({'return': 'typing.Iterator[int]'}, [], 'x()[0]'), + ({'return': 'typing.Iterator[int]'}, ['int'], 'next(x())'), + ({'return': 'typing.Iterable[float]'}, ['float'], 'list(x())[0]'), + + ({'return': 'decimal.Decimal'}, [], ''), + ({'return': 'lalalalallalaa'}, [], ''), + ({'return': 'lalalalallalaa.lala'}, [], ''), ] ) -def test_string_annotation(annotations, result): +def test_string_annotation(annotations, result, code): x = lambda foo: 1 x.__annotations__ = annotations - defs = jedi.Interpreter('x()', [locals()]).goto_definitions() + defs = jedi.Interpreter(code or 'x()', [locals()]).goto_definitions() assert [d.name for d in defs] == result