Merge branch 'improve-type-annotation-inference-refactors' of https://github.com/PeterJCLaw/jedi

This commit is contained in:
Dave Halter
2020-04-01 00:54:25 +02:00
6 changed files with 213 additions and 188 deletions

View File

@@ -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):

View File

@@ -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([])

View File

@@ -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,10 +268,9 @@ 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(
merge_type_var_dicts(
annotation_variable_results,
_infer_type_vars(ann, actual_value_set),
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(
merge_type_var_dicts(
annotation_variable_results,
_infer_type_vars(v, actual_value_set),
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,44 +316,7 @@ 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):
"""
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__()
def merge_pairwise_generics(annotation_value, annotated_argument_class):
def merge_pairwise_generics(annotation_value, annotated_argument_class):
"""
Match up the generic parameters from the given argument class to the
target annotation.
@@ -390,18 +350,19 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False):
`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
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(
merge_type_var_dicts(
type_var_dict,
_infer_type_vars(
nested_annotation_value,
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.
@@ -409,106 +370,6 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False):
),
)
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
return type_var_dict

View File

@@ -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):

View File

@@ -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__())

View File

@@ -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