diff --git a/jedi/common/value.py b/jedi/common/value.py index 815966b1..84013b17 100644 --- a/jedi/common/value.py +++ b/jedi/common/value.py @@ -10,6 +10,43 @@ class BaseValue(object): return value value = value.parent_context + def infer_type_vars(self, value_set, is_class_value=False): + """ + When the current instance represents a type annotation, this method + tries to find information about undefined type vars and returns a dict + from type var name to value set. + + This is for example important to understand what `iter([1])` returns. + According to typeshed, `iter` returns an `Iterator[_T]`: + + def iter(iterable: Iterable[_T]) -> Iterator[_T]: ... + + This functions would generate `int` for `_T` in this case, because it + unpacks the `Iterable`. + + Parameters + ---------- + + `self`: represents the annotation of the current parameter to infer the + value for. In the above example, this would initially be the + `Iterable[_T]` of the `iterable` parameter and then, when recursing, + just the `_T` generic parameter. + + `value_set`: represents the actual argument passed to the parameter + 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 {} + class BaseValueSet(object): def __init__(self, iterable): diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index 4d14f1a9..891267c8 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -438,6 +438,18 @@ class ValueSet(BaseValueSet): s = 'Optional[%s]' % s return s + def infer_type_vars(self, value_set, is_class_value=False): + # Circular + from jedi.inference.gradual.annotation import merge_type_var_dicts + + type_var_dict = {} + for value in self._set: + merge_type_var_dicts( + type_var_dict, + value.infer_type_vars(value_set, is_class_value), + ) + return type_var_dict + NO_VALUES = ValueSet([]) diff --git a/jedi/inference/gradual/annotation.py b/jedi/inference/gradual/annotation.py index 1b334573..eb6c0872 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -14,7 +14,6 @@ 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.generics import TupleGenericManager -from jedi.inference.gradual.typing import TypingClassValueWithIndex from jedi.inference.gradual.type_var import TypeVar from jedi.inference.helpers import is_string from jedi.inference.compiled import builtin_from_name @@ -269,11 +268,10 @@ def infer_type_vars_for_execution(function, arguments, annotation_dict): elif kind is Parameter.VAR_KEYWORD: # TODO _dict_values is not public. actual_value_set = actual_value_set.try_merge('_dict_values') - for ann in annotation_value_set: - _merge_type_var_dicts( - annotation_variable_results, - _infer_type_vars(ann, actual_value_set), - ) + merge_type_var_dicts( + annotation_variable_results, + annotation_value_set.infer_type_vars(actual_value_set), + ) return annotation_variable_results @@ -302,15 +300,14 @@ def infer_type_vars_for_callable(arguments, lazy_params): callable_param_values = lazy_callable_param.infer() # Infer unknown type var actual_value_set = lazy_value.infer() - for v in callable_param_values: - _merge_type_var_dicts( - annotation_variable_results, - _infer_type_vars(v, actual_value_set), - ) + merge_type_var_dicts( + annotation_variable_results, + callable_param_values.infer_type_vars(actual_value_set), + ) return annotation_variable_results -def _merge_type_var_dicts(base_dict, new_dict): +def merge_type_var_dicts(base_dict, new_dict): for type_var_name, values in new_dict.items(): if values: try: @@ -319,195 +316,59 @@ def _merge_type_var_dicts(base_dict, new_dict): base_dict[type_var_name] = values -def _infer_type_vars(annotation_value, value_set, is_class_value=False): +def merge_pairwise_generics(annotation_value, annotated_argument_class): """ - This function tries to find information about undefined type vars and - returns a dict from type var name to value set. + Match up the generic parameters from the given argument class to the + target annotation. - This is for example important to understand what `iter([1])` returns. - According to typeshed, `iter` returns an `Iterator[_T]`: + This walks the generic parameters immediately within the annotation and + argument's type, in order to determine the concrete values of the + annotation's parameters for the current case. - def iter(iterable: Iterable[_T]) -> Iterator[_T]: ... + For example, given the following code: - This functions would generate `int` for `_T` in this case, because it - unpacks the `Iterable`. + def values(mapping: Mapping[K, V]) -> List[V]: ... + + for val in values({1: 'a'}): + val + + Then this function should be given representations of `Mapping[K, V]` + and `Mapping[int, str]`, so that it can determine that `K` is `int and + `V` is `str`. + + Note that it is responsibility of the caller to traverse the MRO of the + argument type as needed in order to find the type matching the + annotation (in this case finding `Mapping[int, str]` as a parent of + `Dict[int, str]`). Parameters ---------- - `annotation_value`: represents the annotation of the current parameter to - infer the value for. In the above example, this would initially be the - `Iterable[_T]` of the `iterable` parameter and then, when recursing, - just the `_T` generic parameter. + `annotation_value`: represents the annotation to infer the concrete + parameter types of. - `value_set`: represents the actual argument passed to the parameter 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. + `annotated_argument_class`: represents the annotated class of the + argument being passed to the object annotated by `annotation_value`. """ + type_var_dict = {} - annotation_name = annotation_value.py__name__() - def merge_pairwise_generics(annotation_value, annotated_argument_class): - """ - Match up the generic parameters from the given argument class to the - target annotation. + if not isinstance(annotated_argument_class, DefineGenericBase): + return type_var_dict - This walks the generic parameters immediately within the annotation and - argument's type, in order to determine the concrete values of the - annotation's parameters for the current case. + annotation_generics = annotation_value.get_generics() + actual_generics = annotated_argument_class.get_generics() - For example, given the following code: - - def values(mapping: Mapping[K, V]) -> List[V]: ... - - for val in values({1: 'a'}): - val - - Then this function should be given representations of `Mapping[K, V]` - and `Mapping[int, str]`, so that it can determine that `K` is `int and - `V` is `str`. - - Note that it is responsibility of the caller to traverse the MRO of the - argument type as needed in order to find the type matching the - annotation (in this case finding `Mapping[int, str]` as a parent of - `Dict[int, str]`). - - Parameters - ---------- - - `annotation_value`: represents the annotation to infer the concrete - parameter types of. - - `annotated_argument_class`: represents the annotated class of the - argument being passed to the object annotated by `annotation_value`. - """ - if not isinstance(annotated_argument_class, DefineGenericBase): - return - - annotation_generics = annotation_value.get_generics() - actual_generics = annotated_argument_class.get_generics() - - for annotation_generics_set, actual_generic_set in zip(annotation_generics, actual_generics): - for nested_annotation_value in annotation_generics_set: - _merge_type_var_dicts( - type_var_dict, - _infer_type_vars( - nested_annotation_value, - actual_generic_set, - # This is a note to ourselves that we have already - # converted the instance representation to its class. - is_class_value=True, - ), - ) - - if isinstance(annotation_value, TypeVar): - if not is_class_value: - return {annotation_name: value_set.py__class__()} - return {annotation_name: value_set} - elif isinstance(annotation_value, TypingClassValueWithIndex): - if annotation_name == 'Type': - given = annotation_value.get_generics() - if given: - if is_class_value: - for element in value_set: - element_name = element.py__name__() - if annotation_name == element_name: - merge_pairwise_generics(annotation_value, element) - - else: - for nested_annotation_value in given[0]: - _merge_type_var_dicts( - type_var_dict, - _infer_type_vars( - nested_annotation_value, - value_set, - is_class_value=True, - ), - ) - - elif annotation_name == 'Callable': - given = annotation_value.get_generics() - if len(given) == 2: - for nested_annotation_value in given[1]: - _merge_type_var_dicts( - type_var_dict, - _infer_type_vars( - nested_annotation_value, - value_set.execute_annotation(), - ), - ) - - elif annotation_name == 'Tuple': - annotation_generics = annotation_value.get_generics() - tuple_annotation, = annotation_value.execute_annotation() - # TODO: is can we avoid using this private method? - if tuple_annotation._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. - for nested_annotation_value in annotation_generics[0]: - _merge_type_var_dicts( - type_var_dict, - _infer_type_vars( - nested_annotation_value, - value_set.merge_types_of_iterate(), - ), - ) - - else: - # The parameter annotation has only explicit type parameters - # (e.g: `Tuple[T]`, `Tuple[T, U]`, `Tuple[T, U, V]`, etc.) so we - # treat the incoming values as needing to match the annotation - # exactly, just as we would for non-tuple annotations. - - for element in value_set: - py_class = element.get_annotated_class_object() - if not isinstance(py_class, GenericClass): - py_class = element - - merge_pairwise_generics(annotation_value, py_class) - - elif isinstance(annotation_value, GenericClass): - if annotation_name == 'Iterable' and not is_class_value: - given = annotation_value.get_generics() - if given: - for nested_annotation_value in given[0]: - _merge_type_var_dicts( - type_var_dict, - _infer_type_vars( - nested_annotation_value, - 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 element in value_set: - if element.api_type == u'function': - # Functions & methods don't have an MRO and we're not - # expecting a Callable (those are handled separately above). - continue - - if element.is_instance(): - py_class = element.get_annotated_class_object() - else: - py_class = element - - for parent_class in py_class.py__mro__(): - class_name = parent_class.py__name__() - if annotation_name == class_name: - merge_pairwise_generics(annotation_value, parent_class) - break + 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, + ), + ) return type_var_dict diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index 3cd8e509..e47b566c 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -200,6 +200,46 @@ 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): + # 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: + 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 element in value_set: + if element.api_type == u'function': + # Functions & methods don't have an MRO and we're not + # expecting a Callable (those are handled separately within + # TypingClassValueWithIndex). + continue + + if element.is_instance(): + py_class = element.get_annotated_class_object() + else: + py_class = element + + for parent_class in py_class.py__mro__(): + class_name = parent_class.py__name__() + if annotation_name == class_name: + merge_type_var_dicts( + type_var_dict, + merge_pairwise_generics(self, parent_class), + ) + break + + return type_var_dict + class _LazyGenericBaseClass(object): def __init__(self, class_value, lazy_base_class): diff --git a/jedi/inference/gradual/type_var.py b/jedi/inference/gradual/type_var.py index f618b4d8..a5ce65c6 100644 --- a/jedi/inference/gradual/type_var.py +++ b/jedi/inference/gradual/type_var.py @@ -107,5 +107,11 @@ class TypeVar(BaseTypingValue): def execute_annotation(self): return self._get_classes().execute_annotation() + def infer_type_vars(self, value_set, is_class_value=False): + annotation_name = self.py__name__() + if not is_class_value: + return {annotation_name: value_set.py__class__()} + return {annotation_name: value_set} + 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 928cb7df..943d5d34 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -184,7 +184,44 @@ class _TypingClassMixin(ClassMixin): class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): - pass + 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 + + type_var_dict = {} + annotation_generics = self.get_generics() + + if not annotation_generics: + return type_var_dict + + annotation_name = self.py__name__() + if annotation_name == 'Type': + if is_class_value: + for element in value_set: + element_name = element.py__name__() + if annotation_name == element_name: + 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, + ) + + elif annotation_name == 'Callable': + if len(annotation_generics) == 2: + 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 type_var_dict class ProxyTypingClassValue(_TypingClassMixin, ProxyTypingValue): @@ -281,6 +318,38 @@ class Tuple(BaseTypingValueWithGenerics): .py__getattribute__('tuple').execute_annotation() return tuple_ + 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 + from jedi.inference.gradual.base import GenericClass + + 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( + value_set.merge_types_of_iterate(), + ) + + else: + # The parameter annotation has only explicit type parameters + # (e.g: `Tuple[T]`, `Tuple[T, U]`, `Tuple[T, U, V]`, etc.) so we + # treat the incoming values as needing to match the annotation + # exactly, just as we would for non-tuple annotations. + + type_var_dict = {} + for element in value_set: + py_class = element.get_annotated_class_object() + if not isinstance(py_class, GenericClass): + py_class = element + + merge_type_var_dicts( + type_var_dict, + merge_pairwise_generics(self, py_class), + ) + + return type_var_dict + class Generic(BaseTypingValueWithGenerics): pass