diff --git a/jedi/inference/gradual/annotation.py b/jedi/inference/gradual/annotation.py index 1de87962..1b334573 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -331,27 +331,110 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): 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): + """ + 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_value.py__name__(): value_set.py__class__()} - return {annotation_value.py__name__(): value_set} + return {annotation_name: value_set.py__class__()} + return {annotation_name: value_set} elif isinstance(annotation_value, TypingClassValueWithIndex): - name = annotation_value.py__name__() - if name == 'Type': + if annotation_name == 'Type': 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, - is_class_value=True, + 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 name == 'Callable': + + elif annotation_name == 'Callable': given = annotation_value.get_generics() if len(given) == 2: for nested_annotation_value in given[1]: @@ -360,11 +443,41 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): _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): - name = annotation_value.py__name__() - if name == 'Iterable': + if annotation_name == 'Iterable' and not is_class_value: given = annotation_value.get_generics() if given: for nested_annotation_value in given[0]: @@ -372,35 +485,30 @@ def _infer_type_vars(annotation_value, value_set, is_class_value=False): type_var_dict, _infer_type_vars( nested_annotation_value, - value_set.merge_types_of_iterate() - ) + value_set.merge_types_of_iterate(), + ), ) - elif name == 'Mapping': - given = annotation_value.get_generics() - if len(given) == 2: - for value in value_set: - try: - method = value.get_mapping_item_values - except AttributeError: - continue - key_values, value_values = method() + 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 nested_annotation_value in given[0]: - _merge_type_var_dicts( - type_var_dict, - _infer_type_vars( - nested_annotation_value, - key_values, - ) - ) - for nested_annotation_value in given[1]: - _merge_type_var_dicts( - type_var_dict, - _infer_type_vars( - nested_annotation_value, - value_values, - ) - ) return type_var_dict diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index 85a34396..928cb7df 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -245,12 +245,7 @@ class Callable(BaseTypingValueWithGenerics): return infer_return_for_callable(arguments, param_values, result_values) -class Tuple(LazyValueWrapper): - def __init__(self, parent_context, name, generics_manager): - self.inference_state = parent_context.inference_state - self.parent_context = parent_context - self._generics_manager = generics_manager - +class Tuple(BaseTypingValueWithGenerics): def _is_homogenous(self): # To specify a variable-length tuple of homogeneous type, Tuple[T, ...] # is used. diff --git a/jedi/inference/value/iterable.py b/jedi/inference/value/iterable.py index b56c98cb..3fc39e52 100644 --- a/jedi/inference/value/iterable.py +++ b/jedi/inference/value/iterable.py @@ -328,6 +328,11 @@ class SequenceLiteralValue(Sequence): self.array_type = SequenceLiteralValue.mapping[atom.children[0]] """The builtin name of the array (list, set, tuple or dict).""" + def _get_generics(self): + if self.array_type == u'tuple': + return tuple(x.infer().py__class__() for x in self.py__iter__()) + return super(SequenceLiteralValue, self)._get_generics() + def py__simple_getitem__(self, index): """Here the index is an int/str. Raises IndexError/KeyError.""" if isinstance(index, slice): diff --git a/test/completion/pep0484_generic_mismatches.py b/test/completion/pep0484_generic_mismatches.py new file mode 100644 index 00000000..bd7d393a --- /dev/null +++ b/test/completion/pep0484_generic_mismatches.py @@ -0,0 +1,320 @@ +# python >= 3.4 +from typing import ( + Callable, + Dict, + Generic, + List, + Sequence, + Tuple, + Type, + TypeVar, +) + +T = TypeVar('T') + + +def foo(x: T) -> T: + return x + + +class CustomGeneric(Generic[T]): + def __init__(self, val: T) -> None: + self.val = val + + +class PlainClass(object): + pass + + +tpl = ("1", 2) +tpl_typed = ("2", 3) # type: Tuple[str, int] + +collection = {"a": 1} +collection_typed = {"a": 1} # type: Dict[str, int] + +list_of_funcs = [foo] # type: List[Callable[[T], T]] + +custom_generic = CustomGeneric(123.45) + +plain_instance = PlainClass() + + +# Test that simple parameters are handled +def list_t_to_list_t(the_list: List[T]) -> List[T]: + return the_list + +x0 = list_t_to_list_t("abc")[0] +#? +x0 + +x1 = list_t_to_list_t(foo)[0] +#? +x1 + +x2 = list_t_to_list_t(tpl)[0] +#? +x2 + +x3 = list_t_to_list_t(tpl_typed)[0] +#? +x3 + +x4 = list_t_to_list_t(collection)[0] +#? +x4 + +x5 = list_t_to_list_t(collection_typed)[0] +#? +x5 + +x6 = list_t_to_list_t(custom_generic)[0] +#? +x6 + +x7 = list_t_to_list_t(plain_instance)[0] +#? +x7 + +for a in list_t_to_list_t(12): + #? + a + + +# Test that simple parameters are handled +def list_type_t_to_list_t(the_list: List[Type[T]]) -> List[T]: + return [x() for x in the_list] + +x0 = list_type_t_to_list_t("abc")[0] +#? +x0 + +x1 = list_type_t_to_list_t(foo)[0] +#? +x1 + +x2 = list_type_t_to_list_t(tpl)[0] +#? +x2 + +x3 = list_type_t_to_list_t(tpl_typed)[0] +#? +x3 + +x4 = list_type_t_to_list_t(collection)[0] +#? +x4 + +x5 = list_type_t_to_list_t(collection_typed)[0] +#? +x5 + +x6 = list_type_t_to_list_t(custom_generic)[0] +#? +x6 + +x7 = list_type_t_to_list_t(plain_instance)[0] +#? +x7 + +for a in list_type_t_to_list_t(12): + #? + a + + +x0 = list_type_t_to_list_t(["abc"])[0] +#? +x0 + +x1 = list_type_t_to_list_t([foo])[0] +#? +x1 + +x2 = list_type_t_to_list_t([tpl])[0] +#? +x2 + +x3 = list_type_t_to_list_t([tpl_typed])[0] +#? +x3 + +x4 = list_type_t_to_list_t([collection])[0] +#? +x4 + +x5 = list_type_t_to_list_t([collection_typed])[0] +#? +x5 + +x6 = list_type_t_to_list_t([custom_generic])[0] +#? +x6 + +x7 = list_type_t_to_list_t([plain_instance])[0] +#? +x7 + +for a in list_type_t_to_list_t([12]): + #? + a + + +def list_func_t_to_list_t(the_list: List[Callable[[T], T]]) -> List[T]: + # Not actually a viable signature, but should be enough to test our handling + # of the generic parameters. + pass + + +x0 = list_func_t_to_list_t("abc")[0] +#? +x0 + +x1 = list_func_t_to_list_t(foo)[0] +#? +x1 + +x2 = list_func_t_to_list_t(tpl)[0] +#? +x2 + +x3 = list_func_t_to_list_t(tpl_typed)[0] +#? +x3 + +x4 = list_func_t_to_list_t(collection)[0] +#? +x4 + +x5 = list_func_t_to_list_t(collection_typed)[0] +#? +x5 + +x6 = list_func_t_to_list_t(custom_generic)[0] +#? +x6 + +x7 = list_func_t_to_list_t(plain_instance)[0] +#? +x7 + +for a in list_func_t_to_list_t(12): + #? + a + + +# The following are all actually wrong, however we're mainly testing here that +# we don't error when processing invalid values, rather than that we get the +# right output. + +x0 = list_func_t_to_list_t(["abc"])[0] +#? str() +x0 + +x2 = list_func_t_to_list_t([tpl])[0] +#? tuple() +x2 + +x3 = list_func_t_to_list_t([tpl_typed])[0] +#? tuple() +x3 + +x4 = list_func_t_to_list_t([collection])[0] +#? dict() +x4 + +x5 = list_func_t_to_list_t([collection_typed])[0] +#? dict() +x5 + +x6 = list_func_t_to_list_t([custom_generic])[0] +#? CustomGeneric() +x6 + +x7 = list_func_t_to_list_t([plain_instance])[0] +#? PlainClass() +x7 + +for a in list_func_t_to_list_t([12]): + #? int() + a + + +def tuple_t(tuple_in: Tuple[T]]) -> Sequence[T]: + return tuple_in + + +x0 = list_t_to_list_t("abc")[0] +#? +x0 + +x1 = list_t_to_list_t(foo)[0] +#? +x1 + +x2 = list_t_to_list_t(tpl)[0] +#? +x2 + +x3 = list_t_to_list_t(tpl_typed)[0] +#? +x3 + +x4 = list_t_to_list_t(collection)[0] +#? +x4 + +x5 = list_t_to_list_t(collection_typed)[0] +#? +x5 + +x6 = list_t_to_list_t(custom_generic)[0] +#? +x6 + +x7 = list_t_to_list_t(plain_instance)[0] +#? +x7 + +for a in list_t_to_list_t(12): + #? + a + + +def tuple_t_elipsis(tuple_in: Tuple[T, ...]]) -> Sequence[T]: + return tuple_in + + +x0 = list_t_to_list_t("abc")[0] +#? +x0 + +x1 = list_t_to_list_t(foo)[0] +#? +x1 + +x2 = list_t_to_list_t(tpl)[0] +#? +x2 + +x3 = list_t_to_list_t(tpl_typed)[0] +#? +x3 + +x4 = list_t_to_list_t(collection)[0] +#? +x4 + +x5 = list_t_to_list_t(collection_typed)[0] +#? +x5 + +x6 = list_t_to_list_t(custom_generic)[0] +#? +x6 + +x7 = list_t_to_list_t(plain_instance)[0] +#? +x7 + +for a in list_t_to_list_t(12): + #? + a diff --git a/test/completion/pep0484_generic_parameters.py b/test/completion/pep0484_generic_parameters.py new file mode 100644 index 00000000..89572d99 --- /dev/null +++ b/test/completion/pep0484_generic_parameters.py @@ -0,0 +1,277 @@ +# python >= 3.4 +from typing import ( + Callable, + Dict, + Generic, + Iterable, + List, + Mapping, + Type, + TypeVar, + Union, +) + +K = TypeVar('K') +T = TypeVar('T') +T_co = TypeVar('T_co', covariant=True) +V = TypeVar('V') + + +list_of_ints = [42] # type: List[int] +list_of_ints_and_strs = [42, 'abc'] # type: List[Union[int, str]] + +# Test that simple parameters are handled +def list_t_to_list_t(the_list: List[T]) -> List[T]: + return the_list + +x0 = list_t_to_list_t(list_of_ints)[0] +#? int() +x0 + +for a in list_t_to_list_t(list_of_ints): + #? int() + a + +# Test that unions are handled +x2 = list_t_to_list_t(list_of_ints_and_strs)[0] +#? int() str() +x2 + +for z in list_t_to_list_t(list_of_ints_and_strs): + #? int() str() + z + + +list_of_int_type = [int] # type: List[Type[int]] + +# Test that nested parameters are handled +def list_type_t_to_list_t(the_list: List[Type[T]]) -> List[T]: + return [x() for x in the_list] + + +x1 = list_type_t_to_list_t(list_of_int_type)[0] +#? int() +x1 + + +for b in list_type_t_to_list_t(list_of_int_type): + #? int() + b + + +def foo(x: T) -> T: + return x + + +list_of_funcs = [foo] # type: List[Callable[[T], T]] + +def list_func_t_to_list_func_type_t(the_list: List[Callable[[T], T]]) -> List[Callable[[Type[T]], T]]: + def adapt(func: Callable[[T], T]) -> Callable[[Type[T]], T]: + def wrapper(typ: Type[T]) -> T: + return func(typ()) + return wrapper + return [adapt(x) for x in the_list] + + +for b in list_func_t_to_list_func_type_t(list_of_funcs): + #? int() + b(int) + + +mapping_int_str = {42: 'a'} # type: Dict[int, str] + +# Test that mappings (that have more than one parameter) are handled +def invert_mapping(mapping: Mapping[K, V]) -> Mapping[V, K]: + return {v: k for k, v in mapping.items()} + +#? int() +invert_mapping(mapping_int_str)['a'] + + +# Test that the right type is chosen when a mapping is passed to something with +# only a single parameter. This checks that our inheritance checking picks the +# right thing. +def first(iterable: Iterable[T]) -> T: + return next(iter(iterable)) + +#? int() +first(mapping_int_str) + +# Test inference of str as an iterable of str. +#? str() +first("abc") + +some_str = NotImplemented # type: str +#? str() +first(some_str) + + +# Test that the right type is chosen when a partially realised mapping is expected +def values(mapping: Mapping[int, T]) -> List[T]: + return list(mapping.values()) + +#? str() +values(mapping_int_str)[0] + +x2 = values(mapping_int_str)[0] +#? str() +x2 + +for b in values(mapping_int_str): + #? str() + b + + +# +# Tests that user-defined generic types are handled +# +list_ints = [42] # type: List[int] + +class CustomGeneric(Generic[T_co]): + def __init__(self, val: T_co) -> None: + self.val = val + + +# Test extraction of type from a custom generic type +def custom(x: CustomGeneric[T]) -> T: + return x.val + +custom_instance = CustomGeneric(42) # type: CustomGeneric[int] + +#? int() +custom(custom_instance) + +x3 = custom(custom_instance) +#? int() +x3 + + +# Test construction of a custom generic type +def wrap_custom(iterable: Iterable[T]) -> List[CustomGeneric[T]]: + return [CustomGeneric(x) for x in iterable] + +#? int() +wrap_custom(list_ints)[0].val + +x4 = wrap_custom(list_ints)[0] +#? int() +x4.val + +for x5 in wrap_custom(list_ints): + #? int() + x5.val + + +# Test extraction of type from a nested custom generic type +list_custom_instances = [CustomGeneric(42)] # type: List[CustomGeneric[int]] + +def unwrap_custom(iterable: Iterable[CustomGeneric[T]]) -> List[T]: + return [x.val for x in iterable] + +#? int() +unwrap_custom(list_custom_instances)[0] + +x6 = unwrap_custom(list_custom_instances)[0] +#? int() +x6 + +for x7 in unwrap_custom(list_custom_instances): + #? int() + x7 + + +for xc in unwrap_custom([CustomGeneric(s) for s in 'abc']): + #? str() + xc + + +for xg in unwrap_custom(CustomGeneric(s) for s in 'abc'): + #? str() + xg + + +# Test extraction of type from type parameer nested within a custom generic type +custom_instance_list_int = CustomGeneric([42]) # type: CustomGeneric[List[int]] + +def unwrap_custom2(instance: CustomGeneric[Iterable[T]]) -> List[T]: + return list(instance.val) + +#? int() +unwrap_custom2(custom_instance_list_int)[0] + +x8 = unwrap_custom2(custom_instance_list_int)[0] +#? int() +x8 + +for x9 in unwrap_custom2(custom_instance_list_int): + #? int() + x9 + + +# Test that classes which have generic parents but are not generic themselves +# are still inferred correctly. +class Specialised(Mapping[int, str]): + pass + + +specialised_instance = NotImplemented # type: Specialised + +#? int() +first(specialised_instance) + +#? str() +values(specialised_instance)[0] + + +# Test that classes which have generic ancestry but neither they nor their +# parents are not generic are still inferred correctly. +class ChildOfSpecialised(Specialised): + pass + + +child_of_specialised_instance = NotImplemented # type: ChildOfSpecialised + +#? int() +first(child_of_specialised_instance) + +#? str() +values(child_of_specialised_instance)[0] + + +# Test that unbound generics are inferred as much as possible +class CustomPartialGeneric1(Mapping[str, T]): + pass + + +custom_partial1_instance = NotImplemented # type: CustomPartialGeneric1[int] + +#? str() +first(custom_partial1_instance) + + +custom_partial1_unbound_instance = NotImplemented # type: CustomPartialGeneric1 + +#? str() +first(custom_partial1_unbound_instance) + + +class CustomPartialGeneric2(Mapping[T, str]): + pass + + +custom_partial2_instance = NotImplemented # type: CustomPartialGeneric2[int] + +#? int() +first(custom_partial2_instance) + +#? str() +values(custom_partial2_instance)[0] + + +custom_partial2_unbound_instance = NotImplemented # type: CustomPartialGeneric2 + +#? [] +first(custom_partial2_unbound_instance) + +#? str() +values(custom_partial2_unbound_instance)[0] diff --git a/test/completion/pep0484_generic_passthroughs.py b/test/completion/pep0484_generic_passthroughs.py new file mode 100644 index 00000000..3ccdae7a --- /dev/null +++ b/test/completion/pep0484_generic_passthroughs.py @@ -0,0 +1,128 @@ +# python >= 3.4 +from typing import Any, Iterable, List, Sequence, Tuple, TypeVar, Union + +T = TypeVar('T') +U = TypeVar('U') +TList = TypeVar('TList', bound=List[Any]) + +untyped_list_str = ['abc', 'def'] +typed_list_str = ['abc', 'def'] # type: List[str] + +untyped_tuple_str = ('abc',) +typed_tuple_str = ('abc',) # type: Tuple[str] + +untyped_tuple_str_int = ('abc', 4) +typed_tuple_str_int = ('abc', 4) # type: Tuple[str, int] + +variadic_tuple_str = ('abc',) # type: Tuple[str, ...] +variadic_tuple_str_int = ('abc', 4) # type: Tuple[Union[str, int], ...] + + +def untyped_passthrough(x): + return x + +def typed_list_generic_passthrough(x: List[T]) -> List[T]: + return x + +def typed_tuple_generic_passthrough(x: Tuple[T]) -> Tuple[T]: + return x + +def typed_multi_typed_tuple_generic_passthrough(x: Tuple[T, U]) -> Tuple[U, T]: + return x[1], x[0] + +def typed_variadic_tuple_generic_passthrough(x: Tuple[T, ...]) -> Sequence[T]: + return x + +def typed_iterable_generic_passthrough(x: Iterable[T]) -> Iterable[T]: + return x + +def typed_fully_generic_passthrough(x: T) -> T: + return x + +def typed_bound_generic_passthrough(x: TList) -> TList: + return x + + +for a in untyped_passthrough(untyped_list_str): + #? str() + a + +for b in untyped_passthrough(typed_list_str): + #? str() + b + + +for c in typed_list_generic_passthrough(untyped_list_str): + #? str() + c + +for d in typed_list_generic_passthrough(typed_list_str): + #? str() + d + + +for e in typed_iterable_generic_passthrough(untyped_list_str): + #? str() + e + +for f in typed_iterable_generic_passthrough(typed_list_str): + #? str() + f + + +for g in typed_tuple_generic_passthrough(untyped_tuple_str): + #? str() + g + +for h in typed_tuple_generic_passthrough(typed_tuple_str): + #? str() + h + + +out_untyped = typed_multi_typed_tuple_generic_passthrough(untyped_tuple_str_int) +#? int() +out_untyped[0] +#? str() +out_untyped[1] + + +out_typed = typed_multi_typed_tuple_generic_passthrough(typed_tuple_str_int) +#? int() +out_typed[0] +#? str() +out_typed[1] + + +for j in typed_variadic_tuple_generic_passthrough(untyped_tuple_str_int): + #? str() int() + j + +for k in typed_variadic_tuple_generic_passthrough(typed_tuple_str_int): + #? str() int() + k + +for l in typed_variadic_tuple_generic_passthrough(variadic_tuple_str): + #? str() + l + +for m in typed_variadic_tuple_generic_passthrough(variadic_tuple_str_int): + #? str() int() + m + + +for n in typed_fully_generic_passthrough(untyped_list_str): + #? str() + n + +for o in typed_fully_generic_passthrough(typed_list_str): + #? str() + o + + +for p in typed_bound_generic_passthrough(untyped_list_str): + #? str() + p + +for q in typed_bound_generic_passthrough(typed_list_str): + #? str() + q diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index 2172e189..0099eaa4 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -340,8 +340,14 @@ def test_math_fuzzy_completion(Script, environment): def test_file_fuzzy_completion(Script): path = os.path.join(test_dir, 'completion') script = Script('"{}/ep08_i'.format(path)) - assert ['pep0484_basic.py"', 'pep0484_typing.py"'] \ - == [comp.name for comp in script.complete(fuzzy=True)] + expected = [ + 'pep0484_basic.py"', + 'pep0484_generic_mismatches.py"', + 'pep0484_generic_parameters.py"', + 'pep0484_generic_passthroughs.py"', + 'pep0484_typing.py"', + ] + assert expected == [comp.name for comp in script.complete(fuzzy=True)] @pytest.mark.parametrize(