1
0
forked from VimPlug/jedi

Properly convert compiled values to generic classes

This commit is contained in:
Dave Halter
2020-01-10 15:07:11 +01:00
parent cac73f2d44
commit 3ba68b5bc6
5 changed files with 74 additions and 25 deletions

View File

@@ -5,6 +5,7 @@ import sys
import operator as op import operator as op
from collections import namedtuple from collections import namedtuple
import warnings import warnings
import re
from jedi._compatibility import unicode, is_py3, builtins, \ from jedi._compatibility import unicode, is_py3, builtins, \
py_version, force_unicode py_version, force_unicode
@@ -491,10 +492,15 @@ class DirectObjectAccess(object):
if sys.version_info < (3, 5): if sys.version_info < (3, 5):
return None, () return None, ()
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 import typing
args = typing.get_args(self._obj) args = typing.get_args(self._obj)
origin = typing.get_origin(self._obj)
name = None if origin is None else str(origin)
return name, tuple(self._create_access_path(arg) for arg in args) return name, tuple(self._create_access_path(arg) for arg in args)
def needs_type_completions(self): def needs_type_completions(self):

View File

@@ -273,9 +273,20 @@ class CompiledObject(Value):
return ValueSet([self]) return ValueSet([self])
name, args = self.access_handle.get_annotation_name_and_args() name, args = self.access_handle.get_annotation_name_and_args()
arguments = [create_from_access_path(self.inference_state, path) for path in args] arguments = [
if name == 'typing.Union': 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) 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() return super(CompiledObject, self).execute_annotation()
def negate(self): def negate(self):

View File

@@ -16,7 +16,7 @@ from jedi.inference.names import NameWrapper, ValueName
from jedi.inference.value.klass import ClassMixin from jedi.inference.value.klass import ClassMixin
from jedi.inference.gradual.base import BaseTypingValue, BaseTypingValueWithGenerics from jedi.inference.gradual.base import BaseTypingValue, BaseTypingValueWithGenerics
from jedi.inference.gradual.type_var import TypeVarClass 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() _PROXY_CLASS_TYPES = 'Tuple Generic Protocol Callable Type'.split()
_TYPE_ALIAS_TYPES = { _TYPE_ALIAS_TYPES = {
@@ -144,6 +144,14 @@ class ProxyTypingValue(BaseTypingValue):
index_class = TypingValueWithIndex index_class = TypingValueWithIndex
py__simple_getitem__ = None 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): def py__getitem__(self, index_value_set, contextualized_node):
return ValueSet( return ValueSet(
self.index_class.create_cached( self.index_class.create_cached(

View File

@@ -286,6 +286,13 @@ class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase
for index_value in index_value_set 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): def define_generics(self, type_var_dict):
from jedi.inference.gradual.base import GenericClass from jedi.inference.gradual.base import GenericClass

View File

@@ -643,29 +643,46 @@ def bar():
@pytest.mark.skipif(sys.version_info < (3, 5), reason="Ignore Python 2, because EOL") @pytest.mark.skipif(sys.version_info < (3, 5), reason="Ignore Python 2, because EOL")
@pytest.mark.parametrize( @pytest.mark.parametrize(
'annotations, result', [ 'annotations, result, code', [
({}, []), ({}, [], ''),
(None, []), (None, [], ''),
({'asdf': 'str'}, []), ({'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. # typing is available via globals.
({'return': 'typing.Union[str, int]'}, ['int', 'str']), ({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''),
({'return': 'typing.Union["str", int]'}, ['int']), ({'return': 'typing.Union["str", int]'}, ['int'], ''),
({'return': 'typing.Union["str", 1]'}, []), ({'return': 'typing.Union["str", 1]'}, [], ''),
({'return': 'typing.Optional[str]'}, ['NoneType', 'str']), ({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''),
({'return': 'typing.Optional[str, int]'}, []), # Takes only one arg ({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg
({'return': 'typing.Any'}, [], ''),
({'return': 'decimal.Decimal'}, []), ({'return': 'typing.Tuple[int, str]'}, ['tuple'], ''),
({'return': 'lalalalallalaa'}, []), ({'return': 'typing.Tuple[int, str]'}, ['int'], 'x()[0]'),
({'return': 'lalalalallalaa.lala'}, []), ({'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 = lambda foo: 1
x.__annotations__ = annotations 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 assert [d.name for d in defs] == result