diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index 3a82e20e..e7ac4baa 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -268,7 +268,7 @@ class Value(HelperValueMixin): def get_type_hint(self, add_class_info=True): return None - def infer_type_vars(self, value_set, is_class_value=False): + def infer_type_vars(self, value_set): """ When the current instance represents a type annotation, this method tries to find information about undefined type vars and returns a dict @@ -294,14 +294,6 @@ class Value(HelperValueMixin): we're inferrined for, or (for recursive calls) their types. In the above example this would first be the representation of the list `[1]` and then, when recursing, just of `1`. - - `is_class_value`: tells us whether or not to treat the `value_set` as - representing the instances or types being passed, which is neccesary - to correctly cope with `Type[T]` annotations. When it is True, this - means that we are being called with a nested portion of an - annotation and that the `value_set` represents the types of the - arguments, rather than their actual instances. Note: not all - recursive calls will neccesarily set this to True. """ return {} @@ -538,7 +530,7 @@ class ValueSet(object): s = 'Optional[%s]' % s return s - def infer_type_vars(self, value_set, is_class_value=False): + def infer_type_vars(self, value_set): # Circular from jedi.inference.gradual.annotation import merge_type_var_dicts @@ -546,7 +538,7 @@ class ValueSet(object): for value in self._set: merge_type_var_dicts( type_var_dict, - value.infer_type_vars(value_set, is_class_value), + value.infer_type_vars(value_set), ) return type_var_dict diff --git a/jedi/inference/gradual/annotation.py b/jedi/inference/gradual/annotation.py index b636240f..7311bfb5 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -12,7 +12,7 @@ from parso import ParserSyntaxError, parse from jedi._compatibility import force_unicode, Parameter from jedi.inference.cache import inference_state_method_cache from jedi.inference.base_value import ValueSet, NO_VALUES -from jedi.inference.gradual.base import DefineGenericBase, GenericClass +from jedi.inference.gradual.base import DefineGenericBaseClass, GenericClass from jedi.inference.gradual.generics import TupleGenericManager from jedi.inference.gradual.type_var import TypeVar from jedi.inference.helpers import is_string @@ -229,7 +229,7 @@ def infer_return_types(function, arguments): return ValueSet.from_sets( ann.define_generics(type_var_dict) - if isinstance(ann, (DefineGenericBase, TypeVar)) else ValueSet({ann}) + if isinstance(ann, (DefineGenericBaseClass, TypeVar)) else ValueSet({ann}) for ann in annotation_values ).execute_annotation() @@ -276,17 +276,17 @@ def infer_return_for_callable(arguments, param_values, result_values): all_type_vars = {} for pv in param_values: if pv.array_type == 'list': - type_var_dict = infer_type_vars_for_callable(arguments, pv.py__iter__()) + type_var_dict = _infer_type_vars_for_callable(arguments, pv.py__iter__()) all_type_vars.update(type_var_dict) return ValueSet.from_sets( v.define_generics(all_type_vars) - if isinstance(v, (DefineGenericBase, TypeVar)) else ValueSet({v}) + if isinstance(v, (DefineGenericBaseClass, TypeVar)) else ValueSet({v}) for v in result_values ).execute_annotation() -def infer_type_vars_for_callable(arguments, lazy_params): +def _infer_type_vars_for_callable(arguments, lazy_params): """ Infers type vars for the Calllable class: @@ -350,7 +350,7 @@ def merge_pairwise_generics(annotation_value, annotated_argument_class): type_var_dict = {} - if not isinstance(annotated_argument_class, DefineGenericBase): + if not isinstance(annotated_argument_class, DefineGenericBaseClass): return type_var_dict annotation_generics = annotation_value.get_generics() @@ -359,12 +359,7 @@ def merge_pairwise_generics(annotation_value, annotated_argument_class): for annotation_generics_set, actual_generic_set in zip(annotation_generics, actual_generics): merge_type_var_dicts( type_var_dict, - annotation_generics_set.infer_type_vars( - actual_generic_set, - # This is a note to ourselves that we have already - # converted the instance representation to its class. - is_class_value=True, - ), + annotation_generics_set.infer_type_vars(actual_generic_set.execute_annotation()), ) return type_var_dict diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index 70cfa447..1143c284 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -23,8 +23,8 @@ class _BoundTypeVarName(AbstractNameDefinition): def iter_(): for value in self._value_set: # Replace any with the constraints if they are there. - from jedi.inference.gradual.typing import Any - if isinstance(value, Any): + from jedi.inference.gradual.typing import AnyClass + if isinstance(value, AnyClass): for constraint in self._type_var.constraints: yield constraint else: @@ -81,7 +81,7 @@ class _AnnotatedClassContext(ClassContext): yield self._value.get_type_var_filter() -class DefineGenericBase(LazyValueWrapper): +class DefineGenericBaseClass(LazyValueWrapper): def __init__(self, generics_manager): self._generics_manager = generics_manager @@ -99,7 +99,7 @@ class DefineGenericBase(LazyValueWrapper): for generic_set in self.get_generics(): values = NO_VALUES for generic in generic_set: - if isinstance(generic, (DefineGenericBase, TypeVar)): + if isinstance(generic, (DefineGenericBaseClass, TypeVar)): result = generic.define_generics(type_var_dict) values |= result if result != ValueSet({generic}): @@ -119,7 +119,7 @@ class DefineGenericBase(LazyValueWrapper): )]) def is_same_class(self, other): - if not isinstance(other, DefineGenericBase): + if not isinstance(other, DefineGenericBaseClass): return False if self.tree_node != other.tree_node: @@ -151,7 +151,7 @@ class DefineGenericBase(LazyValueWrapper): ) -class GenericClass(ClassMixin, DefineGenericBase): +class GenericClass(ClassMixin, DefineGenericBaseClass): """ A class that is defined with generics, might be something simple like: @@ -200,29 +200,27 @@ class GenericClass(ClassMixin, DefineGenericBase): return True return self._class_value.is_sub_class_of(class_value) - def infer_type_vars(self, value_set, is_class_value=False): + def infer_type_vars(self, value_set): # Circular from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts annotation_name = self.py__name__() type_var_dict = {} - if annotation_name == 'Iterable' and not is_class_value: + if annotation_name == 'Iterable': annotation_generics = self.get_generics() if annotation_generics: return annotation_generics[0].infer_type_vars( value_set.merge_types_of_iterate(), ) - else: # Note: we need to handle the MRO _in order_, so we need to extract # the elements from the set first, then handle them, even if we put # them back in a set afterwards. for py_class in value_set: - if not is_class_value: - if py_class.is_instance() and not py_class.is_compiled(): - py_class = py_class.get_annotated_class_object() - else: - continue + if py_class.is_instance() and not py_class.is_compiled(): + py_class = py_class.get_annotated_class_object() + else: + continue if py_class.api_type != u'class': # Functions & modules don't have an MRO and we're not @@ -332,10 +330,9 @@ class _PseudoTreeNameClass(Value): yield EmptyFilter() def py__class__(self): - # TODO this is obviously not correct, but at least gives us a class if - # we have none. Some of these objects don't really have a base class in - # typeshed. - return builtin_from_name(self.inference_state, u'object') + # This might not be 100% correct, but it is good enough. The details of + # the typing library are not really an issue for Jedi. + return builtin_from_name(self.inference_state, u'type') @property def name(self): @@ -365,9 +362,9 @@ class BaseTypingValue(LazyValueWrapper): return '%s(%s)' % (self.__class__.__name__, self._tree_name.value) -class BaseTypingValueWithGenerics(DefineGenericBase): +class BaseTypingClassWithGenerics(DefineGenericBaseClass): def __init__(self, parent_context, tree_name, generics_manager): - super(BaseTypingValueWithGenerics, self).__init__(generics_manager) + super(BaseTypingClassWithGenerics, self).__init__(generics_manager) self.inference_state = parent_context.inference_state self.parent_context = parent_context self._tree_name = tree_name @@ -378,3 +375,29 @@ class BaseTypingValueWithGenerics(DefineGenericBase): def __repr__(self): return '%s(%s%s)' % (self.__class__.__name__, self._tree_name.value, self._generics_manager) + + +class BaseTypingInstance(LazyValueWrapper): + def __init__(self, parent_context, class_value, tree_name, generics_manager): + self.inference_state = class_value.inference_state + self.parent_context = parent_context + self._class_value = class_value + self._tree_name = tree_name + self._generics_manager = generics_manager + + def py__class__(self): + return self._class_value + + def get_annotated_class_object(self): + return self._class_value + + @property + def name(self): + return ValueName(self, self._tree_name) + + def _get_wrapped_value(self): + object_, = builtin_from_name(self.inference_state, u'object').execute_annotation() + return object_ + + def __repr__(self): + return '<%s: %s>' % (self.__class__.__name__, self._generics_manager) diff --git a/jedi/inference/gradual/type_var.py b/jedi/inference/gradual/type_var.py index a5ce65c6..9b041df0 100644 --- a/jedi/inference/gradual/type_var.py +++ b/jedi/inference/gradual/type_var.py @@ -107,11 +107,9 @@ class TypeVar(BaseTypingValue): def execute_annotation(self): return self._get_classes().execute_annotation() - def infer_type_vars(self, value_set, is_class_value=False): + def infer_type_vars(self, value_set): annotation_name = self.py__name__() - if not is_class_value: - return {annotation_name: value_set.py__class__()} - return {annotation_name: value_set} + return {annotation_name: value_set.py__class__()} def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.py__name__()) diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 189fe997..add28177 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -17,7 +17,8 @@ from jedi.inference.arguments import repack_with_argument_clinic from jedi.inference.filters import FilterWrapper 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.base import BaseTypingValue, \ + BaseTypingClassWithGenerics, BaseTypingInstance from jedi.inference.gradual.type_var import TypeVarClass from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager @@ -66,7 +67,7 @@ class TypingModuleName(NameWrapper): yield TypeVarClass.create_cached( inference_state, self.parent_context, self.tree_name) elif name == 'Any': - yield Any.create_cached( + yield AnyClass.create_cached( inference_state, self.parent_context, self.tree_name) elif name == 'TYPE_CHECKING': # This is needed for e.g. imports that are only available for type @@ -84,7 +85,7 @@ class TypingModuleName(NameWrapper): elif name == 'TypedDict': # TODO doesn't even exist in typeshed/typing.py, yet. But will be # added soon. - yield TypedDictBase.create_cached( + yield TypedDictClass.create_cached( inference_state, self.parent_context, self.tree_name) elif name in ('no_type_check', 'no_type_check_decorator'): # This is not necessary, as long as we are not doing type checking. @@ -100,7 +101,7 @@ class TypingModuleFilterWrapper(FilterWrapper): name_wrapper_class = TypingModuleName -class TypingValueWithIndex(BaseTypingValueWithGenerics): +class TypingClassWithIndex(BaseTypingClassWithGenerics): def execute_annotation(self): string_name = self._tree_name.value @@ -129,6 +130,7 @@ class TypingValueWithIndex(BaseTypingValueWithGenerics): cls = mapped[string_name] return ValueSet([cls( self.parent_context, + self, self._tree_name, generics_manager=self._generics_manager, )]) @@ -137,7 +139,7 @@ class TypingValueWithIndex(BaseTypingValueWithGenerics): return ValueSet.from_sets(self._generics_manager.to_tuple()) def _create_instance_with_generics(self, generics_manager): - return TypingValueWithIndex( + return TypingClassWithIndex( self.parent_context, self._tree_name, generics_manager @@ -145,7 +147,7 @@ class TypingValueWithIndex(BaseTypingValueWithGenerics): class ProxyTypingValue(BaseTypingValue): - index_class = TypingValueWithIndex + index_class = TypingClassWithIndex def with_generics(self, generics_tuple): return self.index_class.create_cached( @@ -183,11 +185,8 @@ class _TypingClassMixin(ClassMixin): return ValueName(self, self._tree_name) -class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): - def infer_type_vars(self, value_set, is_class_value=False): - # Circular - from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts - +class TypingClassValueWithIndex(_TypingClassMixin, TypingClassWithIndex): + def infer_type_vars(self, value_set): type_var_dict = {} annotation_generics = self.get_generics() @@ -196,49 +195,22 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): annotation_name = self.py__name__() if annotation_name == 'Type': - if is_class_value: - # This only applies if we are comparing something like - # List[Type[int]] with Iterable[Type[int]]. First, Jedi tries to - # match List/Iterable. After that we will land here, because - # is_class_value will be True at that point. Obviously we also - # compare below that both sides are `Type`. - for element in value_set: - element_name = element.py__name__() - if element_name == 'Type': - merge_type_var_dicts( - type_var_dict, - merge_pairwise_generics(self, element), - ) - else: - return annotation_generics[0].infer_type_vars( - value_set, - is_class_value=True, - ) + return annotation_generics[0].infer_type_vars( + # This is basically a trick to avoid extra code: We execute the + # incoming classes to be able to use the normal code for type + # var inference. + value_set.execute_annotation(), + ) elif annotation_name == 'Callable': if len(annotation_generics) == 2: - if is_class_value: - # This only applies if we are comparing something like - # List[Callable[..., T]] with Iterable[Callable[..., T]]. - # First, Jedi tries to match List/Iterable. After that we - # will land here, because is_class_value will be True at - # that point. Obviously we also compare below that both - # sides are `Callable`. - for element in value_set: - element_name = element.py__name__() - if element_name == 'Callable': - merge_type_var_dicts( - type_var_dict, - merge_pairwise_generics(self, element), - ) - else: - return annotation_generics[1].infer_type_vars( - value_set.execute_annotation(), - ) + return annotation_generics[1].infer_type_vars( + value_set.execute_annotation(), + ) elif annotation_name == 'Tuple': tuple_annotation, = self.execute_annotation() - return tuple_annotation.infer_type_vars(value_set, is_class_value) + return tuple_annotation.infer_type_vars(value_set) return type_var_dict @@ -284,7 +256,7 @@ class TypeAlias(LazyValueWrapper): return ValueSet([self._get_wrapped_value()]) -class Callable(BaseTypingValueWithGenerics): +class Callable(BaseTypingInstance): def py__call__(self, arguments): """ def x() -> Callable[[Callable[..., _T]], _T]: ... @@ -301,7 +273,7 @@ class Callable(BaseTypingValueWithGenerics): return infer_return_for_callable(arguments, param_values, result_values) -class Tuple(BaseTypingValueWithGenerics): +class Tuple(BaseTypingInstance): def _is_homogenous(self): # To specify a variable-length tuple of homogeneous type, Tuple[T, ...] # is used. @@ -337,28 +309,23 @@ class Tuple(BaseTypingValueWithGenerics): .py__getattribute__('tuple').execute_annotation() return tuple_ - def infer_type_vars(self, value_set, is_class_value=False): + @property + def name(self): + return self._wrapped_value.name + + def infer_type_vars(self, value_set): # Circular from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts - from jedi.inference.gradual.base import GenericClass value_set = value_set.filter( lambda x: x.py__name__().lower() == 'tuple', ) - # Somewhat unusually, this `infer_type_vars` method is on an instance - # representation of a type, rather than the annotation or class - # representation. This means that as a starting point, we need to - # convert the incoming values to their instance style if they're - # classes, rather than the reverse. - if is_class_value: - value_set = value_set.execute_annotation() - if self._is_homogenous(): # The parameter annotation is of the form `Tuple[T, ...]`, # so we treat the incoming tuple like a iterable sequence # rather than a positional container of elements. - return self.get_generics()[0].infer_type_vars( + return self._class_value.get_generics()[0].infer_type_vars( value_set.merge_types_of_iterate(), ) @@ -370,30 +337,32 @@ class Tuple(BaseTypingValueWithGenerics): type_var_dict = {} for element in value_set: - if not is_class_value: - py_class = element.get_annotated_class_object() - if not isinstance(py_class, GenericClass): - py_class = element - else: - py_class = element + try: + method = element.get_annotated_class_object + except AttributeError: + # This might still happen, because the tuple name matching + # above is not 100% correct, so just catch the remaining + # cases here. + continue + py_class = method() merge_type_var_dicts( type_var_dict, - merge_pairwise_generics(self, py_class), + merge_pairwise_generics(self._class_value, py_class), ) return type_var_dict -class Generic(BaseTypingValueWithGenerics): +class Generic(BaseTypingInstance): pass -class Protocol(BaseTypingValueWithGenerics): +class Protocol(BaseTypingInstance): pass -class Any(BaseTypingValue): +class AnyClass(BaseTypingValue): def execute_annotation(self): debug.warning('Used Any - returned no results') return NO_VALUES @@ -447,7 +416,7 @@ class CastFunction(BaseTypingValue): return type_value_set.execute_annotation() -class TypedDictBase(BaseTypingValue): +class TypedDictClass(BaseTypingValue): """ This class has no responsibilities and is just here to make sure that typed dicts can be identified. diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index 89bc7925..ec51b24a 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -241,7 +241,7 @@ class ClassMixin(object): def is_typeddict(self): # TODO Do a proper mro resolution. Currently we are just listing # classes. However, it's a complicated algorithm. - from jedi.inference.gradual.typing import TypedDictBase + from jedi.inference.gradual.typing import TypedDictClass for lazy_cls in self.py__bases__(): if not isinstance(lazy_cls, LazyTreeValue): return False @@ -253,7 +253,7 @@ class ClassMixin(object): return False for cls in lazy_cls.infer(): - if isinstance(cls, TypedDictBase): + if isinstance(cls, TypedDictClass): return True try: method = cls.is_typeddict diff --git a/test/completion/pep0484_generic_parameters.py b/test/completion/pep0484_generic_parameters.py index 9edcd061..3898b17f 100644 --- a/test/completion/pep0484_generic_parameters.py +++ b/test/completion/pep0484_generic_parameters.py @@ -10,6 +10,7 @@ from typing import ( Type, TypeVar, Union, + Sequence, ) K = TypeVar('K') @@ -165,6 +166,9 @@ some_str = NotImplemented # type: str #? str() first(some_str) +annotated = [len] # type: List[ Callable[[Sequence[float]], int] ] +#? int() +first(annotated)() # Test that the right type is chosen when a partially realised mapping is expected def values(mapping: Mapping[int, T]) -> List[T]: diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index b5d601ce..bae881de 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -656,7 +656,8 @@ def bar(): ({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg ({'return': 'typing.Any'}, [], ''), - ({'return': 'typing.Tuple[int, str]'}, ['tuple'], ''), + ({'return': 'typing.Tuple[int, str]'}, + ['Tuple' if sys.version_info[:2] == (3, 6) else 'tuple'], ''), ({'return': 'typing.Tuple[int, str]'}, ['int'], 'x()[0]'), ({'return': 'typing.Tuple[int, str]'}, ['str'], 'x()[1]'), ({'return': 'typing.Tuple[int, str]'}, [], 'x()[2]'),