From 5bd6a9c16402801d6aa4cc7b159fff7a110b5717 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Wed, 18 Mar 2020 20:49:03 +0000 Subject: [PATCH 1/9] Rename function which is going to be used elsewhere --- jedi/inference/gradual/annotation.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/jedi/inference/gradual/annotation.py b/jedi/inference/gradual/annotation.py index 1b334573..030e67b9 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -270,7 +270,7 @@ def infer_type_vars_for_execution(function, arguments, annotation_dict): # 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( + merge_type_var_dicts( annotation_variable_results, _infer_type_vars(ann, actual_value_set), ) @@ -303,14 +303,14 @@ def infer_type_vars_for_callable(arguments, lazy_params): # Infer unknown type var actual_value_set = lazy_value.infer() for v in callable_param_values: - _merge_type_var_dicts( + merge_type_var_dicts( annotation_variable_results, _infer_type_vars(v, 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: @@ -398,7 +398,7 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): 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( + merge_type_var_dicts( type_var_dict, _infer_type_vars( nested_annotation_value, @@ -425,7 +425,7 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): else: for nested_annotation_value in given[0]: - _merge_type_var_dicts( + merge_type_var_dicts( type_var_dict, _infer_type_vars( nested_annotation_value, @@ -438,7 +438,7 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): given = annotation_value.get_generics() if len(given) == 2: for nested_annotation_value in given[1]: - _merge_type_var_dicts( + merge_type_var_dicts( type_var_dict, _infer_type_vars( nested_annotation_value, @@ -455,7 +455,7 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): # 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( + merge_type_var_dicts( type_var_dict, _infer_type_vars( nested_annotation_value, @@ -481,7 +481,7 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): given = annotation_value.get_generics() if given: for nested_annotation_value in given[0]: - _merge_type_var_dicts( + merge_type_var_dicts( type_var_dict, _infer_type_vars( nested_annotation_value, From dd60a8a4c93d9ee0bfe9e20f8dad7cd78d828fc5 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Wed, 18 Mar 2020 21:01:15 +0000 Subject: [PATCH 2/9] Extract nested function which is going to be used elsewhere --- jedi/inference/gradual/annotation.py | 127 +++++++++++++++------------ 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/jedi/inference/gradual/annotation.py b/jedi/inference/gradual/annotation.py index 030e67b9..4f523f56 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -319,6 +319,65 @@ def merge_type_var_dicts(base_dict, new_dict): base_dict[type_var_name] = values +def merge_pairwise_generics(annotation_value, annotated_argument_class): + """ + Match up the generic parameters from the given argument class to the + target annotation. + + 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. + + 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`. + """ + + type_var_dict = {} + + if not isinstance(annotated_argument_class, DefineGenericBase): + return type_var_dict + + 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, + ), + ) + + return type_var_dict + + def _infer_type_vars(annotation_value, value_set, is_class_value=False): """ This function tries to find information about undefined type vars and @@ -356,59 +415,6 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): 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. - - 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. - - 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__()} @@ -421,7 +427,10 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): for element in value_set: element_name = element.py__name__() if annotation_name == element_name: - merge_pairwise_generics(annotation_value, element) + merge_type_var_dicts( + type_var_dict, + merge_pairwise_generics(annotation_value, element), + ) else: for nested_annotation_value in given[0]: @@ -474,7 +483,10 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): if not isinstance(py_class, GenericClass): py_class = element - merge_pairwise_generics(annotation_value, py_class) + merge_type_var_dicts( + type_var_dict, + merge_pairwise_generics(annotation_value, py_class), + ) elif isinstance(annotation_value, GenericClass): if annotation_name == 'Iterable' and not is_class_value: @@ -506,7 +518,10 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): 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) + merge_type_var_dicts( + type_var_dict, + merge_pairwise_generics(annotation_value, parent_class), + ) break return type_var_dict From 3c7621049c9b1a0cef7cd96b642a133ca5c230a9 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Wed, 18 Mar 2020 21:25:17 +0000 Subject: [PATCH 3/9] Extract annotation inference onto annotation classes This removes the _infer_type_vars util in favour of a polymorphic implementation, removing the conditional checks on the type of the annotation instance. While for the moment this creates some circular imports, further refactoring to follow should be able to remove those. --- jedi/common/value.py | 37 +++++++ jedi/inference/gradual/annotation.py | 157 +-------------------------- jedi/inference/gradual/base.py | 42 +++++++ jedi/inference/gradual/type_var.py | 6 + jedi/inference/gradual/typing.py | 74 ++++++++++++- 5 files changed, 161 insertions(+), 155 deletions(-) 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/gradual/annotation.py b/jedi/inference/gradual/annotation.py index 4f523f56..13c84295 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 @@ -272,7 +271,7 @@ def infer_type_vars_for_execution(function, arguments, annotation_dict): for ann in annotation_value_set: merge_type_var_dicts( annotation_variable_results, - _infer_type_vars(ann, actual_value_set), + ann.infer_type_vars(actual_value_set), ) return annotation_variable_results @@ -305,7 +304,7 @@ def infer_type_vars_for_callable(arguments, lazy_params): for v in callable_param_values: merge_type_var_dicts( annotation_variable_results, - _infer_type_vars(v, actual_value_set), + v.infer_type_vars(actual_value_set), ) return annotation_variable_results @@ -366,8 +365,7 @@ def merge_pairwise_generics(annotation_value, annotated_argument_class): for nested_annotation_value in annotation_generics_set: merge_type_var_dicts( type_var_dict, - _infer_type_vars( - nested_annotation_value, + nested_annotation_value.infer_type_vars( actual_generic_set, # This is a note to ourselves that we have already # converted the instance representation to its class. @@ -378,155 +376,6 @@ def merge_pairwise_generics(annotation_value, annotated_argument_class): return type_var_dict -def _infer_type_vars(annotation_value, value_set, is_class_value=False): - """ - This function 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 - ---------- - - `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. - - `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. - """ - type_var_dict = {} - annotation_name = annotation_value.py__name__() - - 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_type_var_dicts( - type_var_dict, - 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_type_var_dicts( - type_var_dict, - 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_type_var_dicts( - type_var_dict, - merge_pairwise_generics(annotation_value, parent_class), - ) - break - - return type_var_dict - - def find_type_from_comment_hint_for(context, node, name): return _find_type_from_comment_hint(context, node, node.children[1], name) diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index f9ccddb2..e8678587 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -200,6 +200,48 @@ 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: + given = self.get_generics() + if given: + for nested_annotation_value in given[0]: + merge_type_var_dicts( + type_var_dict, + nested_annotation_value.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 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_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 03873c52..5e8ae3db 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -180,7 +180,79 @@ 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 + from jedi.inference.gradual.base import GenericClass + + annotation_name = self.py__name__() + type_var_dict = {} + if annotation_name == 'Type': + given = self.get_generics() + if given: + 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: + for nested_annotation_value in given[0]: + merge_type_var_dicts( + type_var_dict, + nested_annotation_value.infer_type_vars( + value_set, + is_class_value=True, + ), + ) + + elif annotation_name == 'Callable': + given = self.get_generics() + if len(given) == 2: + for nested_annotation_value in given[1]: + merge_type_var_dicts( + type_var_dict, + nested_annotation_value.infer_type_vars( + value_set.execute_annotation(), + ), + ) + + elif annotation_name == 'Tuple': + annotation_generics = self.get_generics() + tuple_annotation, = self.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, + nested_annotation_value.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. + + 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 ProxyTypingClassValue(_TypingClassMixin, ProxyTypingValue): From f68d65ed59a6b81a6e3d06aa7b63c642765fd2df Mon Sep 17 00:00:00 2001 From: Peter Law Date: Wed, 18 Mar 2020 21:44:18 +0000 Subject: [PATCH 4/9] Push much looping and merging of infering type vars into ValueSet --- jedi/inference/base_value.py | 12 ++++++++ jedi/inference/gradual/annotation.py | 37 ++++++++++++------------- jedi/inference/gradual/base.py | 13 ++++----- jedi/inference/gradual/typing.py | 41 +++++++++++++--------------- 4 files changed, 54 insertions(+), 49 deletions(-) diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index 05134773..89cec757 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -437,6 +437,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 13c84295..eb6c0872 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -268,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, - ann.infer_type_vars(actual_value_set), - ) + merge_type_var_dicts( + annotation_variable_results, + annotation_value_set.infer_type_vars(actual_value_set), + ) return annotation_variable_results @@ -301,11 +300,10 @@ 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, - v.infer_type_vars(actual_value_set), - ) + merge_type_var_dicts( + annotation_variable_results, + callable_param_values.infer_type_vars(actual_value_set), + ) return annotation_variable_results @@ -362,16 +360,15 @@ def merge_pairwise_generics(annotation_value, annotated_argument_class): 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, - nested_annotation_value.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, - ), - ) + 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 e8678587..6a3081d4 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -209,13 +209,12 @@ class GenericClass(ClassMixin, DefineGenericBase): if annotation_name == 'Iterable' and not is_class_value: given = self.get_generics() if given: - for nested_annotation_value in given[0]: - merge_type_var_dicts( - type_var_dict, - nested_annotation_value.infer_type_vars( - value_set.merge_types_of_iterate(), - ), - ) + merge_type_var_dicts( + type_var_dict, + given[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 diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 5e8ae3db..08695f2a 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -200,25 +200,23 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): ) else: - for nested_annotation_value in given[0]: - merge_type_var_dicts( - type_var_dict, - nested_annotation_value.infer_type_vars( - value_set, - is_class_value=True, - ), - ) + merge_type_var_dicts( + type_var_dict, + given[0].infer_type_vars( + value_set, + is_class_value=True, + ), + ) elif annotation_name == 'Callable': given = self.get_generics() if len(given) == 2: - for nested_annotation_value in given[1]: - merge_type_var_dicts( - type_var_dict, - nested_annotation_value.infer_type_vars( - value_set.execute_annotation(), - ), - ) + merge_type_var_dicts( + type_var_dict, + given[1].infer_type_vars( + value_set.execute_annotation(), + ), + ) elif annotation_name == 'Tuple': annotation_generics = self.get_generics() @@ -228,13 +226,12 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): # 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, - nested_annotation_value.infer_type_vars( - value_set.merge_types_of_iterate(), - ), - ) + merge_type_var_dicts( + type_var_dict, + annotation_generics[0].infer_type_vars( + value_set.merge_types_of_iterate(), + ), + ) else: # The parameter annotation has only explicit type parameters From ea33db388b4d2afc4c58ce6ed624f9ecfbf2e6f1 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 22 Mar 2020 15:40:29 +0000 Subject: [PATCH 5/9] Remove dict merging where it doesn't do anything These cases are all at the end of a single-path branch that ends up "merging" against an empty mapping which is then returned unchanged. --- jedi/inference/gradual/base.py | 7 ++----- jedi/inference/gradual/typing.py | 23 +++++++---------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index 6a3081d4..248a8742 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -209,11 +209,8 @@ class GenericClass(ClassMixin, DefineGenericBase): if annotation_name == 'Iterable' and not is_class_value: given = self.get_generics() if given: - merge_type_var_dicts( - type_var_dict, - given[0].infer_type_vars( - value_set.merge_types_of_iterate(), - ), + return given[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 diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 08695f2a..b589a8e1 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -200,22 +200,16 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): ) else: - merge_type_var_dicts( - type_var_dict, - given[0].infer_type_vars( - value_set, - is_class_value=True, - ), + return given[0].infer_type_vars( + value_set, + is_class_value=True, ) elif annotation_name == 'Callable': given = self.get_generics() if len(given) == 2: - merge_type_var_dicts( - type_var_dict, - given[1].infer_type_vars( - value_set.execute_annotation(), - ), + return given[1].infer_type_vars( + value_set.execute_annotation(), ) elif annotation_name == 'Tuple': @@ -226,11 +220,8 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): # 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. - merge_type_var_dicts( - type_var_dict, - annotation_generics[0].infer_type_vars( - value_set.merge_types_of_iterate(), - ), + return annotation_generics[0].infer_type_vars( + value_set.merge_types_of_iterate(), ) else: From 3c90a84f68b4e2a73f3d63d681b3d6ef063234a9 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 22 Mar 2020 15:47:46 +0000 Subject: [PATCH 6/9] Extract common get_generics() calls These no longer need to be guarded by the conditions now that we know these types are generic anyway. --- jedi/inference/gradual/base.py | 7 ++++--- jedi/inference/gradual/typing.py | 13 ++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index 248a8742..32b321fc 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -207,11 +207,12 @@ class GenericClass(ClassMixin, DefineGenericBase): annotation_name = self.py__name__() type_var_dict = {} if annotation_name == 'Iterable' and not is_class_value: - given = self.get_generics() - if given: - return given[0].infer_type_vars( + 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 diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index b589a8e1..03e5a53d 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -187,9 +187,10 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): annotation_name = self.py__name__() type_var_dict = {} + annotation_generics = self.get_generics() + if annotation_name == 'Type': - given = self.get_generics() - if given: + if annotation_generics: if is_class_value: for element in value_set: element_name = element.py__name__() @@ -200,20 +201,18 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): ) else: - return given[0].infer_type_vars( + return annotation_generics[0].infer_type_vars( value_set, is_class_value=True, ) elif annotation_name == 'Callable': - given = self.get_generics() - if len(given) == 2: - return given[1].infer_type_vars( + if len(annotation_generics) == 2: + return annotation_generics[1].infer_type_vars( value_set.execute_annotation(), ) elif annotation_name == 'Tuple': - annotation_generics = self.get_generics() tuple_annotation, = self.execute_annotation() # TODO: is can we avoid using this private method? if tuple_annotation._is_homogenous(): From 525b88e9f1a0670a65da43a50d874afd4dc1c036 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 22 Mar 2020 15:49:31 +0000 Subject: [PATCH 7/9] Simplify early-exit code by having it once --- jedi/inference/gradual/typing.py | 34 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 03e5a53d..7ccb1a9c 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -185,26 +185,28 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts from jedi.inference.gradual.base import GenericClass - annotation_name = self.py__name__() type_var_dict = {} annotation_generics = self.get_generics() - if annotation_name == 'Type': - if annotation_generics: - 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), - ) + if not annotation_generics: + return type_var_dict - else: - return annotation_generics[0].infer_type_vars( - value_set, - is_class_value=True, - ) + 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: From e2090772f3d144372f46ee7031ac7e0799ab1bfa Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sun, 22 Mar 2020 16:04:39 +0000 Subject: [PATCH 8/9] Push tuple handling onto Tuple class This resolves a TODO to avoid using a private method --- jedi/inference/gradual/typing.py | 58 ++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 7ccb1a9c..fd2d5a1e 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -183,7 +183,6 @@ 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 - from jedi.inference.gradual.base import GenericClass type_var_dict = {} annotation_generics = self.get_generics() @@ -216,30 +215,7 @@ class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): elif annotation_name == 'Tuple': tuple_annotation, = self.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. - return annotation_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. - - 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 tuple_annotation.infer_type_vars(value_set, is_class_value) return type_var_dict @@ -338,6 +314,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 From eac5ac8426dc2b05778ff60c61ad8eb4dfa320c2 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Wed, 25 Mar 2020 22:35:12 +0000 Subject: [PATCH 9/9] Update comment after refactor moved code --- jedi/inference/gradual/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index 32b321fc..198c13f0 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -220,7 +220,8 @@ class GenericClass(ClassMixin, DefineGenericBase): 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). + # expecting a Callable (those are handled separately within + # TypingClassValueWithIndex). continue if element.is_instance():