| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 8d30064 commit 4f4877e
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -67,6 +67,7 @@ | |||
| 67 | 67 | UninhabitedType, | |
| 68 | 68 | UnionType, | |
| 69 | 69 | UnpackType, | |
| 70 | + extend_args_for_prefix_and_suffix, | ||
| 70 | 71 | find_unpack_in_list, | |
| 71 | 72 | flatten_nested_unions, | |
| 72 | 73 | get_proper_type, | |
@@ -509,18 +510,17 @@ def visit_instance(self, left: Instance) -> bool: | |||
| 509 | 510 | if isinstance(unpacked, Instance): | |
| 510 | 511 | return self._is_subtype(left, unpacked) | |
| 511 | 512 | if left.type.has_base(right.partial_fallback.type.fullname): | |
| 513 | + mapped = map_instance_to_supertype(left, right.partial_fallback.type) | ||
| 514 | + # Special cases to consider: | ||
| 515 | + # * tuple[Any, ...] instance is a (non-proper) subtype of all tuple types. | ||
| 516 | + # * Foo[*tuple[X, ...]] (normalized) instance is a subtype of all | ||
| 517 | + # tuples with appropriate fallback (e.g. for variadic NamedTuples). | ||
| 512 | 518 | if not self.proper_subtype: | |
| 513 | - # Special cases to consider: | ||
| 514 | - # * Plain tuple[Any, ...] instance is a subtype of all tuple types. | ||
| 515 | - # * Foo[*tuple[Any, ...]] (normalized) instance is a subtype of all | ||
| 516 | - # tuples with fallback to Foo (e.g. for variadic NamedTuples). | ||
| 517 | - mapped = map_instance_to_supertype(left, right.partial_fallback.type) | ||
| 518 | - if is_erased_instance(mapped): | ||
| 519 | - if ( | ||
| 520 | - mapped.type.fullname == "builtins.tuple" | ||
| 521 | - or mapped.type.has_type_var_tuple_type | ||
| 522 | - ): | ||
| 523 | - return True | ||
| 519 | + if is_erased_instance(mapped) and mapped.type.fullname == "builtins.tuple": | ||
| 520 | + return True | ||
| 521 | + if is_normalized_instance(mapped): | ||
| 522 | + if self._is_subtype(mapped, right.partial_fallback): | ||
| 523 | + return True | ||
| 524 | 524 | return False | |
| 525 | 525 | if isinstance(right, TypeVarTupleType): | |
| 526 | 526 | # tuple[Any, ...] is like Any in the world of tuples (see special case above). | |
@@ -819,9 +819,12 @@ def visit_tuple_type(self, left: TupleType) -> bool: | |||
| 819 | 819 | elif isinstance(right, TupleType): | |
| 820 | 820 | # If right has a variadic unpack this needs special handling. If there is a TypeVarTuple | |
| 821 | 821 | # unpack, item count must coincide. If the left has variadic unpack but right | |
| 822 | - # doesn't have one, we will fall through to False down the line. | ||
| 822 | + # doesn't have one, we will fall through. | ||
| 823 | 823 | if self.variadic_tuple_subtype(left, right): | |
| 824 | 824 | return True | |
| 825 | + # The only case where variadic can be subtype of fixed is when left variadic item is Any. | ||
| 826 | + # Otherwise, the original left will be returned, causing fall through to False. | ||
| 827 | + left = self.adjust_left_if_possible(left, right) | ||
| 825 | 828 | if len(left.items) != len(right.items): | |
| 826 | 829 | return False | |
| 827 | 830 | if any(not self._is_subtype(l, r) for l, r in zip(left.items, right.items)): | |
@@ -841,6 +844,57 @@ def visit_tuple_type(self, left: TupleType) -> bool: | |||
| 841 | 844 | else: | |
| 842 | 845 | return False | |
| 843 | 846 | ||
| 847 | + def adjust_left_if_possible(self, left: TupleType, right: TupleType) -> TupleType: | ||
| 848 | + """Adjust shape of left containing *tuple[Any, ...] to match right. | ||
| 849 | + | ||
| 850 | + Note: this only works if right is fixed size (including *Ts), the variadic | ||
| 851 | + right are handled by the caller, currently with variadic_tuple_subtype(). | ||
| 852 | + """ | ||
| 853 | + left_variadic = self.get_variadic_item(left) | ||
| 854 | + if left_variadic is None: | ||
| 855 | + return left | ||
| 856 | + left_unpack_index, left_item = left_variadic | ||
| 857 | + if not isinstance(get_proper_type(left_item), AnyType): | ||
| 858 | + return left | ||
| 859 | + right_unpack_index = find_unpack_in_list(right.items) | ||
| 860 | + if right_unpack_index is None: | ||
| 861 | + if len(left.items) > len(right.items) + 1: | ||
| 862 | + return left | ||
| 863 | + num_anys = len(right.items) - len(left.items) + 1 | ||
| 864 | + return left.copy_modified( | ||
| 865 | + items=left.items[:left_unpack_index] | ||
| 866 | + + [left_item] * num_anys | ||
| 867 | + + left.items[left_unpack_index + 1 :] | ||
| 868 | + ) | ||
| 869 | + right_unpack = right.items[right_unpack_index] | ||
| 870 | + assert isinstance(right_unpack, UnpackType) | ||
| 871 | + right_unpacked = get_proper_type(right_unpack.type) | ||
| 872 | + if isinstance(right_unpacked, Instance): | ||
| 873 | + return left | ||
| 874 | + right_prefix = right_unpack_index | ||
| 875 | + right_suffix = len(right.items) - right_prefix - 1 | ||
| 876 | + left_prefix = left_unpack_index | ||
| 877 | + left_suffix = len(left.items) - left_prefix - 1 | ||
| 878 | + if left_prefix > right_prefix or left_suffix > right_suffix: | ||
| 879 | + return left | ||
| 880 | + new_items = extend_args_for_prefix_and_suffix( | ||
| 881 | + tuple(left.items), right_prefix, right_suffix | ||
| 882 | + ) | ||
| 883 | + return left.copy_modified(items=list(new_items)) | ||
| 884 | + | ||
| 885 | + def get_variadic_item(self, tup: TupleType) -> tuple[int, Type] | None: | ||
| 886 | + """If this is tuple[X, *tuple[Y, ...], Z], return Y, otherwise None.""" | ||
| 887 | + unpack_index = find_unpack_in_list(tup.items) | ||
| 888 | + if unpack_index is None: | ||
| 889 | + return None | ||
| 890 | + unpack = tup.items[unpack_index] | ||
| 891 | + assert isinstance(unpack, UnpackType) | ||
| 892 | + unpacked = get_proper_type(unpack.type) | ||
| 893 | + if not isinstance(unpacked, Instance): | ||
| 894 | + return None | ||
| 895 | + assert unpacked.type.fullname == "builtins.tuple" | ||
| 896 | + return unpack_index, unpacked.args[0] | ||
| 897 | + | ||
| 844 | 898 | def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: | |
| 845 | 899 | """Check subtyping between two potentially variadic tuples. | |
| 846 | 900 | ||
@@ -850,18 +904,11 @@ def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: | |||
| 850 | 904 | Note: the cases where right is fixed or has *Ts unpack should be handled | |
| 851 | 905 | by the caller. | |
| 852 | 906 | """ | |
| 853 | - right_unpack_index = find_unpack_in_list(right.items) | ||
| 854 | - if right_unpack_index is None: | ||
| 907 | + right_variadic = self.get_variadic_item(right) | ||
| 908 | + if right_variadic is None: | ||
| 855 | 909 | # This case should be handled by the caller. | |
| 856 | 910 | return False | |
| 857 | - right_unpack = right.items[right_unpack_index] | ||
| 858 | - assert isinstance(right_unpack, UnpackType) | ||
| 859 | - right_unpacked = get_proper_type(right_unpack.type) | ||
| 860 | - if not isinstance(right_unpacked, Instance): | ||
| 861 | - # This case should be handled by the caller. | ||
| 862 | - return False | ||
| 863 | - assert right_unpacked.type.fullname == "builtins.tuple" | ||
| 864 | - right_item = right_unpacked.args[0] | ||
| 911 | + right_unpack_index, right_item = right_variadic | ||
| 865 | 912 | right_prefix = right_unpack_index | |
| 866 | 913 | right_suffix = len(right.items) - right_prefix - 1 | |
| 867 | 914 | left_unpack_index = find_unpack_in_list(left.items) | |
@@ -883,16 +930,16 @@ def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: | |||
| 883 | 930 | return False | |
| 884 | 931 | return all(self._is_subtype(li, right_item) for li in middle) | |
| 885 | 932 | else: | |
| 886 | - if len(left.items) < len(right.items): | ||
| 887 | - # There are some items on the left that will never have a matching length | ||
| 888 | - # on the right. | ||
| 889 | - return False | ||
| 890 | 933 | left_prefix = left_unpack_index | |
| 891 | 934 | left_suffix = len(left.items) - left_prefix - 1 | |
| 892 | 935 | left_unpack = left.items[left_unpack_index] | |
| 893 | 936 | assert isinstance(left_unpack, UnpackType) | |
| 894 | 937 | left_unpacked = get_proper_type(left_unpack.type) | |
| 895 | 938 | if not isinstance(left_unpacked, Instance): | |
| 939 | + if len(left.items) < len(right.items): | ||
| 940 | + # There are some items on the left that will never have a matching length | ||
| 941 | + # on the right. | ||
| 942 | + return False | ||
| 896 | 943 | # *Ts unpack can't be split, except if it is all mapped to Anys or objects. | |
| 897 | 944 | if self.is_top_type(right_item): | |
| 898 | 945 | right_prefix_types, middle, right_suffix_types = split_with_prefix_and_suffix( | |
@@ -914,6 +961,12 @@ def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: | |||
| 914 | 961 | # subtyping: *each* item on the left, must be a subtype of *some* item on the right. | |
| 915 | 962 | # For this we first check the "asymptotic case", i.e. that both unpacks a subtypes, | |
| 916 | 963 | # and then check subtyping for all finite overlaps. | |
| 964 | + # Note: if the left item is Any we use any() semantics instead of all(). | ||
| 965 | + use_any = isinstance(get_proper_type(left_item), AnyType) | ||
| 966 | + if not use_any and len(left.items) < len(right.items): | ||
| 967 | + # There are some items on the left that will never have a matching length | ||
| 968 | + # on the right. | ||
| 969 | + return False | ||
| 917 | 970 | if not self._is_subtype(left_item, right_item): | |
| 918 | 971 | return False | |
| 919 | 972 | max_overlap = max(0, right_prefix - left_prefix, right_suffix - left_suffix) | |
@@ -923,8 +976,11 @@ def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: | |||
| 923 | 976 | repr_items += left.items[-left_suffix:] | |
| 924 | 977 | left_repr = left.copy_modified(items=repr_items) | |
| 925 | 978 | if not self._is_subtype(left_repr, right): | |
| 926 | - return False | ||
| 927 | - return True | ||
| 979 | + if not use_any: | ||
| 980 | + return False | ||
| 981 | + elif use_any: | ||
| 982 | + return True | ||
| 983 | + return not use_any | ||
| 928 | 984 | ||
| 929 | 985 | def is_top_type(self, typ: Type) -> bool: | |
| 930 | 986 | if not self.proper_subtype and isinstance(get_proper_type(typ), AnyType): | |
@@ -2384,3 +2440,21 @@ def is_erased_instance(t: Instance) -> bool: | |||
| 2384 | 2440 | elif not isinstance(get_proper_type(arg), AnyType): | |
| 2385 | 2441 | return False | |
| 2386 | 2442 | return True | |
| 2443 | + | ||
| 2444 | + | ||
| 2445 | + def is_normalized_instance(t: Instance) -> bool: | ||
| 2446 | + """Is this instance type a normalized representation of a tuple type? | ||
| 2447 | + | ||
| 2448 | + Type like class C[T, *Ts](tuple[T, *Ts]) are internally represented as | ||
| 2449 | + tuple types, so that e.g. C[int, str] is tuple[int, str, fallback=C[int, str]]. | ||
| 2450 | + However, in case of a variadic argument they are normalized, similar to how | ||
| 2451 | + tuple[*tuple[int, ...]] (TupleType) is normalized to tuple[int, ...] (Instance). | ||
| 2452 | + For example, C[int, *tuple[str, ...]] is represented as an instance. This | ||
| 2453 | + function detects such instances, when they need special-casing. | ||
| 2454 | + """ | ||
| 2455 | + if not t.args: | ||
| 2456 | + return False | ||
| 2457 | + if not t.type.tuple_type: | ||
| 2458 | + return False | ||
| 2459 | + expanded = expand_type_by_instance(t.type.tuple_type, t) | ||
| 2460 | + return isinstance(expanded, Instance) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2917,3 +2917,139 @@ def g(x: K) -> None: | |||
| 2917 | 2917 | assert d is not None | |
| 2918 | 2918 | reveal_type(d) # N: Revealed type is "tuple[K | list[int], ...] | tuple[*tuple[int | list[int], ...], int]" | |
| 2919 | 2919 | [builtins fixtures/tuple.pyi] | |
| 2920 | + | ||
| 2921 | + [case testVariadicTupleSubtypingWithAnyBasic] | ||
| 2922 | + from typing import Any, Unpack | ||
| 2923 | + | ||
| 2924 | + Ge1 = tuple[int, Unpack[tuple[Any, ...]]] | ||
| 2925 | + | ||
| 2926 | + def ge1_to_0(shape: Ge1) -> tuple[()]: | ||
| 2927 | + return shape # E: Incompatible return value type (got "tuple[int, Unpack[tuple[Any, ...]]]", expected "tuple[()]") | ||
| 2928 | + def ge1_to_1(shape: Ge1) -> tuple[int]: | ||
| 2929 | + return shape | ||
| 2930 | + def ge1_to_2(shape: Ge1) -> tuple[int, int]: | ||
| 2931 | + return shape | ||
| 2932 | + [builtins fixtures/tuple.pyi] | ||
| 2933 | + | ||
| 2934 | + [case testVariadicTupleSubtypingWithAnyExtra] | ||
| 2935 | + from typing import Any, Unpack | ||
| 2936 | + | ||
| 2937 | + def foo(x: tuple[int, str]) -> None: ... | ||
| 2938 | + def bar(x: tuple[int, int]) -> None: ... | ||
| 2939 | + | ||
| 2940 | + t1: tuple[int, Unpack[tuple[Any, ...]], str] | ||
| 2941 | + t2: tuple[Unpack[tuple[Any, ...]], int, str] | ||
| 2942 | + t3: tuple[int, str, Unpack[tuple[Any, ...]]] | ||
| 2943 | + t4: tuple[int, Unpack[tuple[Any, ...]], int] | ||
| 2944 | + foo(t1) | ||
| 2945 | + foo(t2) | ||
| 2946 | + foo(t3) | ||
| 2947 | + foo(t4) # E: Argument 1 to "foo" has incompatible type "tuple[int, Unpack[tuple[Any, ...]], int]"; expected "tuple[int, str]" | ||
| 2948 | + | ||
| 2949 | + tt1: tuple[int, Unpack[tuple[Any, ...]], int, int] | ||
| 2950 | + tt2: tuple[int, int, Unpack[tuple[Any, ...]], int] | ||
| 2951 | + tt3: tuple[Unpack[tuple[Any, ...]], int] | ||
| 2952 | + tt4: tuple[Unpack[tuple[Any, ...]], str] | ||
| 2953 | + bar(tt1) # E: Argument 1 to "bar" has incompatible type "tuple[int, Unpack[tuple[Any, ...]], int, int]"; expected "tuple[int, int]" | ||
| 2954 | + bar(tt2) # E: Argument 1 to "bar" has incompatible type "tuple[int, int, Unpack[tuple[Any, ...]], int]"; expected "tuple[int, int]" | ||
| 2955 | + bar(tt3) | ||
| 2956 | + bar(tt4) # E: Argument 1 to "bar" has incompatible type "tuple[Unpack[tuple[Any, ...]], str]"; expected "tuple[int, int]" | ||
| 2957 | + [builtins fixtures/tuple.pyi] | ||
| 2958 | + | ||
| 2959 | + [case testVariadicTupleSubtypingWithAnyExtraTypeVar] | ||
| 2960 | + from typing import Any, Unpack, TypeVarTuple | ||
| 2961 | + | ||
| 2962 | + t1: tuple[int, Unpack[tuple[Any, ...]], int] | ||
| 2963 | + t2: tuple[int, int, Unpack[tuple[Any, ...]], int, int] | ||
| 2964 | + t3: tuple[int, int, int, Unpack[tuple[Any, ...]], int, int, int] | ||
| 2965 | + | ||
| 2966 | + Ts = TypeVarTuple("Ts") | ||
| 2967 | + def test(x: tuple[int, int, Unpack[Ts], int, int]) -> None: | ||
| 2968 | + x = t1 | ||
| 2969 | + x = t2 | ||
| 2970 | + x = t3 # E: Incompatible types in assignment (expression has type "tuple[int, int, int, Unpack[tuple[Any, ...]], int, int, int]", variable has type "tuple[int, int, Unpack[Ts], int, int]") | ||
| 2971 | + [builtins fixtures/tuple.pyi] | ||
| 2972 | + | ||
| 2973 | + [case testVariadicTupleSubtypingWithAnyExtraVariadic] | ||
| 2974 | + from typing import Any, Unpack | ||
| 2975 | + | ||
| 2976 | + def foo(x: tuple[int, Unpack[tuple[int, ...]], int]) -> None: ... | ||
| 2977 | + | ||
| 2978 | + t1: tuple[int, Unpack[tuple[Any, ...]]] | ||
| 2979 | + t2: tuple[Unpack[tuple[Any, ...]], int] | ||
| 2980 | + t3: tuple[Unpack[tuple[Any, ...]], str] | ||
| 2981 | + t4: tuple[Unpack[tuple[Any, ...]], int, int, int] | ||
| 2982 | + | ||
| 2983 | + foo(t1) | ||
| 2984 | + foo(t2) | ||
| 2985 | + foo(t3) # E: Argument 1 to "foo" has incompatible type "tuple[Unpack[tuple[Any, ...]], str]"; expected "tuple[int, Unpack[tuple[int, ...]], int]" | ||
| 2986 | + foo(t4) | ||
| 2987 | + [builtins fixtures/tuple.pyi] | ||
| 2988 | + | ||
| 2989 | + [case testVariadicTupleSubclassSubtypingWithAny] | ||
| 2990 | + from typing import Any, Generic, Unpack, TypeVar, TypeVarTuple | ||
| 2991 | + | ||
| 2992 | + T = TypeVar("T") | ||
| 2993 | + Ts = TypeVarTuple("Ts") | ||
| 2994 | + class Array(tuple[Unpack[Ts]], Generic[T, Unpack[Ts]]): ... | ||
| 2995 | + | ||
| 2996 | + Float2D = Array[float, int, int] | ||
| 2997 | + FloatND = Array[float, Unpack[tuple[Any, ...]]] | ||
| 2998 | + | ||
| 2999 | + def from_2d(a: Float2D) -> FloatND: | ||
| 3000 | + return a | ||
| 3001 | + | ||
| 3002 | + def into_2d(a: FloatND) -> Float2D: | ||
| 3003 | + return a | ||
| 3004 | + [builtins fixtures/tuple.pyi] | ||
| 3005 | + | ||
| 3006 | + [case testVariadicInstanceSubtypingWithAny] | ||
| 3007 | + from typing import Any, Generic, Unpack, TypeVarTuple | ||
| 3008 | + | ||
| 3009 | + Ts = TypeVarTuple("Ts") | ||
| 3010 | + class C(Generic[Unpack[Ts]]): ... | ||
| 3011 | + | ||
| 3012 | + def foo(x: C[int, str]) -> None: ... | ||
| 3013 | + def bar(x: C[int, int]) -> None: ... | ||
| 3014 | + def baz(x: C[int, int, int]) -> None: ... | ||
| 3015 | + | ||
| 3016 | + t1: C[int, Unpack[tuple[Any, ...]], str] | ||
| 3017 | + t2: C[Unpack[tuple[Any, ...]], int, str] | ||
| 3018 | + t3: C[int, str, Unpack[tuple[Any, ...]]] | ||
| 3019 | + t4: C[int, Unpack[tuple[Any, ...]], int] | ||
| 3020 | + foo(t1) | ||
| 3021 | + foo(t2) | ||
| 3022 | + foo(t3) | ||
| 3023 | + foo(t4) # E: Argument 1 to "foo" has incompatible type "C[int, Unpack[tuple[Any, ...]], int]"; expected "C[int, str]" | ||
| 3024 | + | ||
| 3025 | + tt1: C[int, Unpack[tuple[Any, ...]], int, int] | ||
| 3026 | + tt2: C[int, int, Unpack[tuple[Any, ...]], int] | ||
| 3027 | + tt3: C[Unpack[tuple[Any, ...]], int] | ||
| 3028 | + tt4: C[Unpack[tuple[Any, ...]], str] | ||
| 3029 | + bar(tt1) # E: Argument 1 to "bar" has incompatible type "C[int, Unpack[tuple[Any, ...]], int, int]"; expected "C[int, int]" | ||
| 3030 | + bar(tt2) # E: Argument 1 to "bar" has incompatible type "C[int, int, Unpack[tuple[Any, ...]], int]"; expected "C[int, int]" | ||
| 3031 | + bar(tt3) | ||
| 3032 | + bar(tt4) # E: Argument 1 to "bar" has incompatible type "C[Unpack[tuple[Any, ...]], str]"; expected "C[int, int]" | ||
| 3033 | + | ||
| 3034 | + baz(tt3) | ||
| 3035 | + baz(t4) | ||
| 3036 | + [builtins fixtures/tuple.pyi] | ||
| 3037 | + | ||
| 3038 | + [case testVariadicInstanceSubtypingWithAnyVariadic] | ||
| 3039 | + from typing import Any, Generic, Unpack, TypeVarTuple | ||
| 3040 | + | ||
| 3041 | + Ts = TypeVarTuple("Ts") | ||
| 3042 | + class C(Generic[Unpack[Ts]]): ... | ||
| 3043 | + | ||
| 3044 | + def foo(x: C[int, Unpack[tuple[int, ...]], int]) -> None: ... | ||
| 3045 | + | ||
| 3046 | + t1: C[int, Unpack[tuple[Any, ...]]] | ||
| 3047 | + t2: C[Unpack[tuple[Any, ...]], int] | ||
| 3048 | + t3: C[Unpack[tuple[Any, ...]], str] | ||
| 3049 | + t4: C[Unpack[tuple[Any, ...]], int, int, int] | ||
| 3050 | + | ||
| 3051 | + foo(t1) | ||
| 3052 | + foo(t2) | ||
| 3053 | + foo(t3) # E: Argument 1 to "foo" has incompatible type "C[Unpack[tuple[Any, ...]], str]"; expected "C[int, Unpack[tuple[int, ...]], int]" | ||
| 3054 | + foo(t4) | ||
| 3055 | + [builtins fixtures/tuple.pyi] | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments