From 71b3ee67536ded354ac10555a9bcd474b4013c72 Mon Sep 17 00:00:00 2001 From: VyZhu <119977752+VyZhu@users.noreply.github.com> Date: Wed, 23 Apr 2025 21:47:21 -0400 Subject: [PATCH 01/16] Initial approach for distribute Tuple issue --- mypy/subtypes.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 71b8b0ba59f5..095243be0ca6 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -2,6 +2,7 @@ from collections.abc import Iterable, Iterator from contextlib import contextmanager +from itertools import product from typing import Any, Callable, Final, TypeVar, cast from typing_extensions import TypeAlias as _TypeAlias @@ -34,6 +35,7 @@ ) from mypy.options import Options from mypy.state import state +from mypy.typeops import make_simplified_union from mypy.types import ( MYPYC_NATIVE_INT_NAMES, TUPLE_LIKE_INSTANCE_NAMES, @@ -185,6 +187,27 @@ def is_subtype( # steps we come back to initial call is_subtype(A, B) and immediately return True. with pop_on_exit(type_state.get_assumptions(is_proper=False), left, right): return _is_subtype(left, right, subtype_context, proper_subtype=False) + left = get_proper_type(left) + right = get_proper_type(right) + + # Special case: distribute Tuple unions before fallback subtype check + if isinstance(left, TupleType) and isinstance(right, UnionType): + items = [get_proper_type(item) for item in left.items] + if any(isinstance(item, UnionType) for item in items): + expanded = [] + for item in items: + if isinstance(item, UnionType): + expanded.append(item.items) + else: + expanded.append([item]) + distributed = [] + for combo in product(*expanded): + fb = left.partial_fallback + if hasattr(left, "fallback") and left.fallback is not None: + fb = left.fallback + distributed.append(TupleType(list(combo), fallback=fb)) + simplified = make_simplified_union(distributed) + return _is_subtype(simplified, right, subtype_context, proper_subtype=False) return _is_subtype(left, right, subtype_context, proper_subtype=False) From 383eb17c1d740e2de2e14e133977f38400ea8517 Mon Sep 17 00:00:00 2001 From: VyZhu <119977752+VyZhu@users.noreply.github.com> Date: Thu, 24 Apr 2025 01:26:25 -0400 Subject: [PATCH 02/16] Attempt to fix max recursive depth for distr tuple --- mypy/subtypes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 095243be0ca6..0ec7071bcf44 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -207,6 +207,8 @@ def is_subtype( fb = left.fallback distributed.append(TupleType(list(combo), fallback=fb)) simplified = make_simplified_union(distributed) + if is_equivalent(simplified, right): + return True return _is_subtype(simplified, right, subtype_context, proper_subtype=False) return _is_subtype(left, right, subtype_context, proper_subtype=False) From 010b3283b66b65727ab02edb934f7b8264f3343b Mon Sep 17 00:00:00 2001 From: VyZhu <119977752+VyZhu@users.noreply.github.com> Date: Thu, 24 Apr 2025 13:58:09 -0400 Subject: [PATCH 03/16] Move code of distribute tuple --- mypy/subtypes.py | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 0ec7071bcf44..0f459d3c0cec 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -189,27 +189,6 @@ def is_subtype( return _is_subtype(left, right, subtype_context, proper_subtype=False) left = get_proper_type(left) right = get_proper_type(right) - - # Special case: distribute Tuple unions before fallback subtype check - if isinstance(left, TupleType) and isinstance(right, UnionType): - items = [get_proper_type(item) for item in left.items] - if any(isinstance(item, UnionType) for item in items): - expanded = [] - for item in items: - if isinstance(item, UnionType): - expanded.append(item.items) - else: - expanded.append([item]) - distributed = [] - for combo in product(*expanded): - fb = left.partial_fallback - if hasattr(left, "fallback") and left.fallback is not None: - fb = left.fallback - distributed.append(TupleType(list(combo), fallback=fb)) - simplified = make_simplified_union(distributed) - if is_equivalent(simplified, right): - return True - return _is_subtype(simplified, right, subtype_context, proper_subtype=False) return _is_subtype(left, right, subtype_context, proper_subtype=False) @@ -328,7 +307,23 @@ def _is_subtype( # TODO: should we consider all types proper subtypes of UnboundType and/or # ErasedType as we do for non-proper subtyping. return True - + if isinstance(left, TupleType) and isinstance(right, UnionType): + items = [get_proper_type(item) for item in left.items] + if any(isinstance(item, UnionType) for item in items): + expanded = [] + for item in items: + if isinstance(item, UnionType): + expanded.append(item.items) + else: + expanded.append([item]) + distributed = [] + for combo in product(*expanded): + fb = left.partial_fallback + if hasattr(left, "fallback") and left.fallback is not None: + fb = left.fallback + distributed.append(TupleType(list(combo), fallback=fb)) + simplified = make_simplified_union(distributed) + return _is_subtype(simplified, right, subtype_context, proper_subtype=False) if isinstance(right, UnionType) and not isinstance(left, UnionType): # Normally, when 'left' is not itself a union, the only way # 'left' can be a subtype of the union 'right' is if it is a From 3feb4129fd952314a9bf14e9aff308f25f2c5c27 Mon Sep 17 00:00:00 2001 From: VyZhu <119977752+VyZhu@users.noreply.github.com> Date: Fri, 25 Apr 2025 23:48:38 -0400 Subject: [PATCH 04/16] Clean up and add comments --- mypy/subtypes.py | 56 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 0f459d3c0cec..420407620e5c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -2,7 +2,6 @@ from collections.abc import Iterable, Iterator from contextlib import contextmanager -from itertools import product from typing import Any, Callable, Final, TypeVar, cast from typing_extensions import TypeAlias as _TypeAlias @@ -35,7 +34,6 @@ ) from mypy.options import Options from mypy.state import state -from mypy.typeops import make_simplified_union from mypy.types import ( MYPYC_NATIVE_INT_NAMES, TUPLE_LIKE_INSTANCE_NAMES, @@ -286,6 +284,21 @@ def is_same_type( ) +# This is a helper function used to check for recursive type of distributed tuple +def structurally_recursive(typ: Type, seen: set[Type] | None = None) -> bool: + if seen is None: + seen = set() + typ = get_proper_type(typ) + if typ in seen: + return True + seen.add(typ) + if isinstance(typ, UnionType): + return any(structurally_recursive(item, seen.copy()) for item in typ.items) + if isinstance(typ, TupleType): + return any(structurally_recursive(item, seen.copy()) for item in typ.items) + return False + + # This is a common entry point for subtyping checks (both proper and non-proper). # Never call this private function directly, use the public versions. def _is_subtype( @@ -308,22 +321,29 @@ def _is_subtype( # ErasedType as we do for non-proper subtyping. return True if isinstance(left, TupleType) and isinstance(right, UnionType): - items = [get_proper_type(item) for item in left.items] - if any(isinstance(item, UnionType) for item in items): - expanded = [] - for item in items: - if isinstance(item, UnionType): - expanded.append(item.items) - else: - expanded.append([item]) - distributed = [] - for combo in product(*expanded): - fb = left.partial_fallback - if hasattr(left, "fallback") and left.fallback is not None: - fb = left.fallback - distributed.append(TupleType(list(combo), fallback=fb)) - simplified = make_simplified_union(distributed) - return _is_subtype(simplified, right, subtype_context, proper_subtype=False) + # check only if not recursive type because if recursive type, + # test run into maximum recursive depth reached + if not structurally_recursive(left) and not structurally_recursive(right): + fallback = left.partial_fallback + tuple_items = left.items + if hasattr(left, "fallback") and left.fallback is not None: + fallback = left.fallback + for i in range(len(tuple_items)): + uitems = tuple_items[i] + uitems_type = get_proper_type(uitems) + if isinstance(uitems_type, UnionType): + new_tuples = [ + TupleType( + tuple_items[:i] + [uitem] + tuple_items[i + 1 :], fallback=fallback + ) + for uitem in uitems_type.items + ] + result = [ + _is_subtype(t, right, subtype_context, proper_subtype=False) + for t in new_tuples + ] + inverted_list = [not item for item in result] + return not any(inverted_list) if isinstance(right, UnionType) and not isinstance(left, UnionType): # Normally, when 'left' is not itself a union, the only way # 'left' can be a subtype of the union 'right' is if it is a From 236d4fb4056e69e567776fd04bca82c12f013d33 Mon Sep 17 00:00:00 2001 From: VyZhu <119977752+VyZhu@users.noreply.github.com> Date: Mon, 28 Apr 2025 10:11:02 -0400 Subject: [PATCH 05/16] Change function name to match pattern --- mypy/subtypes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 420407620e5c..850853fc23ae 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -285,7 +285,7 @@ def is_same_type( # This is a helper function used to check for recursive type of distributed tuple -def structurally_recursive(typ: Type, seen: set[Type] | None = None) -> bool: +def is_structurally_recursive(typ: Type, seen: set[Type] | None = None) -> bool: if seen is None: seen = set() typ = get_proper_type(typ) @@ -293,9 +293,9 @@ def structurally_recursive(typ: Type, seen: set[Type] | None = None) -> bool: return True seen.add(typ) if isinstance(typ, UnionType): - return any(structurally_recursive(item, seen.copy()) for item in typ.items) + return any(is_structurally_recursive(item, seen.copy()) for item in typ.items) if isinstance(typ, TupleType): - return any(structurally_recursive(item, seen.copy()) for item in typ.items) + return any(is_structurally_recursive(item, seen.copy()) for item in typ.items) return False @@ -323,7 +323,7 @@ def _is_subtype( if isinstance(left, TupleType) and isinstance(right, UnionType): # check only if not recursive type because if recursive type, # test run into maximum recursive depth reached - if not structurally_recursive(left) and not structurally_recursive(right): + if not is_structurally_recursive(left) and not is_structurally_recursive(right): fallback = left.partial_fallback tuple_items = left.items if hasattr(left, "fallback") and left.fallback is not None: From 5f5f02fd804def299f6b49f9130eb7bcc56c9dbc Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 10:20:08 -0400 Subject: [PATCH 06/16] Add unit tests for distribute tuple --- test-data/unit/check-tuples.test | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 3424d053fe42..a81f2a7f6260 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1815,3 +1815,43 @@ class tuple_aa_subclass(Tuple[A, A]): ... inst_tuple_aa_subclass: tuple_aa_subclass = tuple_aa_subclass((A(), A()))[:] # E: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "tuple_aa_subclass") [builtins fixtures/tuple.pyi] + +# ---------------------------------------------------------------------------- +# Union-in-Tuple distribution +# ---------------------------------------------------------------------------- + +[case testTupleUnionDistributionSuccess] +from typing import Tuple, Optional, Union + +def f1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[float, None]]: + return x + +def f2(x: Tuple[Union[int, str], float]) -> Union[Tuple[int, float], Tuple[str, float]]: + return x + +def f3(x: Tuple[int, Union[str, None]]) -> Union[Tuple[int, str], Tuple[int, None]]: + return x + +def f4(x: Tuple[Union[int, float]]) -> Union[Tuple[int], Tuple[float]]: + return x + +def f5(x: Tuple[Union[int, str], Union[bool, None]]) -> Union[ + Tuple[int, bool], + Tuple[int, None], + Tuple[str, bool], + Tuple[str, None], +]: + return x + +[builtins fixtures/tuple.pyi] + +[case testTupleUnionDistributionFail] +from typing import Tuple, Optional, Union + +def g1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[str, float]]: + return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, float], Tuple[str, float]]") + +def g2(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, str], Tuple[float, None]]: + return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") + +[builtins fixtures/tuple.pyi] From e667eaf6009584cf545d44a80cd5f906d2a00773 Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 10:22:17 -0400 Subject: [PATCH 07/16] Added new line at the end of file to fix lint error --- test-data/unit/check-tuples.test | 1 + 1 file changed, 1 insertion(+) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index a81f2a7f6260..0283843a1083 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1855,3 +1855,4 @@ def g2(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, str], Tuple[float return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") [builtins fixtures/tuple.pyi] + From 7973c3c129a3df629f2bc6a4e262ae0192f87786 Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 10:37:33 -0400 Subject: [PATCH 08/16] Fix lint error --- test-data/unit/check-tuples.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 0283843a1083..3346429d8236 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1852,7 +1852,7 @@ def g1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[str return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, float], Tuple[str, float]]") def g2(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, str], Tuple[float, None]]: - return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") + return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") [builtins fixtures/tuple.pyi] From c30ebcd674e764d4bb881f5709bb3630960506bf Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 10:47:59 -0400 Subject: [PATCH 09/16] Remove new line --- test-data/unit/check-tuples.test | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 3346429d8236..7ed344e1a6f2 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1854,5 +1854,4 @@ def g1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[str def g2(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, str], Tuple[float, None]]: return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") -[builtins fixtures/tuple.pyi] - +[builtins fixtures/tuple.pyi] \ No newline at end of file From e80da9ffbc0675e398295681c90771c8c5b68b44 Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 10:52:50 -0400 Subject: [PATCH 10/16] Attempt to fix lint error --- test-data/unit/check-tuples.test | 80 ++++++++++++++++---------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 7ed344e1a6f2..e32d2efebf0b 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -524,6 +524,46 @@ class A: pass class B: pass [builtins fixtures/tuple.pyi] +# ---------------------------------------------------------------------------- +# Union-in-Tuple distribution +# ---------------------------------------------------------------------------- + +[case testTupleUnionDistributionSuccess] +from typing import Tuple, Optional, Union + +def f1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[float, None]]: + return x + +def f2(x: Tuple[Union[int, str], float]) -> Union[Tuple[int, float], Tuple[str, float]]: + return x + +def f3(x: Tuple[int, Union[str, None]]) -> Union[Tuple[int, str], Tuple[int, None]]: + return x + +def f4(x: Tuple[Union[int, float]]) -> Union[Tuple[int], Tuple[float]]: + return x + +def f5(x: Tuple[Union[int, str], Union[bool, None]]) -> Union[ + Tuple[int, bool], + Tuple[int, None], + Tuple[str, bool], + Tuple[str, None], +]: + return x + +[builtins fixtures/tuple.pyi] + +[case testTupleUnionDistributionFail] +from typing import Tuple, Optional, Union + +def g1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[str, float]]: + return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, float], Tuple[str, float]]") + +def g2(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, str], Tuple[float, None]]: + return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") + +[builtins fixtures/tuple.pyi] + [case testMultipleAssignmentWithNonTupleRvalue] a: A b: B @@ -1814,44 +1854,4 @@ class A: ... class tuple_aa_subclass(Tuple[A, A]): ... inst_tuple_aa_subclass: tuple_aa_subclass = tuple_aa_subclass((A(), A()))[:] # E: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "tuple_aa_subclass") -[builtins fixtures/tuple.pyi] - -# ---------------------------------------------------------------------------- -# Union-in-Tuple distribution -# ---------------------------------------------------------------------------- - -[case testTupleUnionDistributionSuccess] -from typing import Tuple, Optional, Union - -def f1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[float, None]]: - return x - -def f2(x: Tuple[Union[int, str], float]) -> Union[Tuple[int, float], Tuple[str, float]]: - return x - -def f3(x: Tuple[int, Union[str, None]]) -> Union[Tuple[int, str], Tuple[int, None]]: - return x - -def f4(x: Tuple[Union[int, float]]) -> Union[Tuple[int], Tuple[float]]: - return x - -def f5(x: Tuple[Union[int, str], Union[bool, None]]) -> Union[ - Tuple[int, bool], - Tuple[int, None], - Tuple[str, bool], - Tuple[str, None], -]: - return x - -[builtins fixtures/tuple.pyi] - -[case testTupleUnionDistributionFail] -from typing import Tuple, Optional, Union - -def g1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[str, float]]: - return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, float], Tuple[str, float]]") - -def g2(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, str], Tuple[float, None]]: - return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") - [builtins fixtures/tuple.pyi] \ No newline at end of file From 06faa9f283a4b9ba704a39c5a61f7e88b236523a Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 10:55:29 -0400 Subject: [PATCH 11/16] Attempt 2 to fix lint error --- test-data/unit/check-tuples.test | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index e32d2efebf0b..967829e7b9c6 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1854,4 +1854,5 @@ class A: ... class tuple_aa_subclass(Tuple[A, A]): ... inst_tuple_aa_subclass: tuple_aa_subclass = tuple_aa_subclass((A(), A()))[:] # E: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "tuple_aa_subclass") -[builtins fixtures/tuple.pyi] \ No newline at end of file +[builtins fixtures/tuple.pyi] + From 33de4d22761907c1937d512b281e6223aedbe078 Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 10:58:27 -0400 Subject: [PATCH 12/16] Attempt 3 to fix lint error --- test-data/unit/check-tuples.test | 1 - 1 file changed, 1 deletion(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 967829e7b9c6..5dcc2da55f25 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1855,4 +1855,3 @@ class tuple_aa_subclass(Tuple[A, A]): ... inst_tuple_aa_subclass: tuple_aa_subclass = tuple_aa_subclass((A(), A()))[:] # E: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "tuple_aa_subclass") [builtins fixtures/tuple.pyi] - From d9a0a1971a3942c4b0dbe5ba88c4369562f05cd6 Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 11:01:23 -0400 Subject: [PATCH 13/16] Added unit tests for distribute tuple --- test-data/unit/check-tuples.test | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 3424d053fe42..8d91cb561249 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1040,6 +1040,45 @@ reveal_type(x) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.Tes [builtins fixtures/tuple.pyi] [out] +# ---------------------------------------------------------------------------- +# Union-in-Tuple distribution +# ---------------------------------------------------------------------------- + +[case testTupleUnionDistributionSuccess] +from typing import Tuple, Optional, Union + +def f1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[float, None]]: + return x + +def f2(x: Tuple[Union[int, str], float]) -> Union[Tuple[int, float], Tuple[str, float]]: + return x + +def f3(x: Tuple[int, Union[str, None]]) -> Union[Tuple[int, str], Tuple[int, None]]: + return x + +def f4(x: Tuple[Union[int, float]]) -> Union[Tuple[int], Tuple[float]]: + return x + +def f5(x: Tuple[Union[int, str], Union[bool, None]]) -> Union[ + Tuple[int, bool], + Tuple[int, None], + Tuple[str, bool], + Tuple[str, None], +]: + return x + +[builtins fixtures/tuple.pyi] + +[case testTupleUnionDistributionFail] +from typing import Tuple, Optional, Union + +def g1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[str, float]]: + return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, float], Tuple[str, float]]") + +def g2(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, str], Tuple[float, None]]: + return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") + +[builtins fixtures/tuple.pyi] -- Variable-length tuples (Tuple[t, ...] with literal '...') -- --------------------------------------------------------- From ed58e804fcddf0ea2945b56150f0e3a773563807 Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 11:03:53 -0400 Subject: [PATCH 14/16] remove extra copy of the test --- test-data/unit/check-tuples.test | 40 -------------------------------- 1 file changed, 40 deletions(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index d6b78c247065..8d91cb561249 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -524,46 +524,6 @@ class A: pass class B: pass [builtins fixtures/tuple.pyi] -# ---------------------------------------------------------------------------- -# Union-in-Tuple distribution -# ---------------------------------------------------------------------------- - -[case testTupleUnionDistributionSuccess] -from typing import Tuple, Optional, Union - -def f1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[float, None]]: - return x - -def f2(x: Tuple[Union[int, str], float]) -> Union[Tuple[int, float], Tuple[str, float]]: - return x - -def f3(x: Tuple[int, Union[str, None]]) -> Union[Tuple[int, str], Tuple[int, None]]: - return x - -def f4(x: Tuple[Union[int, float]]) -> Union[Tuple[int], Tuple[float]]: - return x - -def f5(x: Tuple[Union[int, str], Union[bool, None]]) -> Union[ - Tuple[int, bool], - Tuple[int, None], - Tuple[str, bool], - Tuple[str, None], -]: - return x - -[builtins fixtures/tuple.pyi] - -[case testTupleUnionDistributionFail] -from typing import Tuple, Optional, Union - -def g1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[str, float]]: - return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, float], Tuple[str, float]]") - -def g2(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, str], Tuple[float, None]]: - return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") - -[builtins fixtures/tuple.pyi] - [case testMultipleAssignmentWithNonTupleRvalue] a: A b: B From 55d0442c006b353be2a3817e8c33ecadd1e5e173 Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 14:10:38 -0400 Subject: [PATCH 15/16] Added blank lines between test cases to fix error --- test-data/unit/check-tuples.test | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 8d91cb561249..5401659137c5 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1040,9 +1040,10 @@ reveal_type(x) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.Tes [builtins fixtures/tuple.pyi] [out] -# ---------------------------------------------------------------------------- -# Union-in-Tuple distribution -# ---------------------------------------------------------------------------- + +-- Union-in-Tuple distribution +-- --------------------------------------------------------- + [case testTupleUnionDistributionSuccess] from typing import Tuple, Optional, Union @@ -1066,8 +1067,8 @@ def f5(x: Tuple[Union[int, str], Union[bool, None]]) -> Union[ Tuple[str, None], ]: return x - [builtins fixtures/tuple.pyi] +[out] [case testTupleUnionDistributionFail] from typing import Tuple, Optional, Union @@ -1077,8 +1078,9 @@ def g1(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, float], Tuple[str def g2(x: Tuple[float, Optional[float]]) -> Union[Tuple[float, str], Tuple[float, None]]: return x # E: Incompatible return value type (got "Tuple[float, Optional[float]]", expected "Union[Tuple[float, str], Tuple[float, None]]") - [builtins fixtures/tuple.pyi] +[out] + -- Variable-length tuples (Tuple[t, ...] with literal '...') -- --------------------------------------------------------- @@ -1854,3 +1856,6 @@ class tuple_aa_subclass(Tuple[A, A]): ... inst_tuple_aa_subclass: tuple_aa_subclass = tuple_aa_subclass((A(), A()))[:] # E: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "tuple_aa_subclass") [builtins fixtures/tuple.pyi] + +-- +-- -------------------- From 50c78fc8b9b9704ecaa1d9d0d3fd6a87d76ec9e8 Mon Sep 17 00:00:00 2001 From: Christina Date: Wed, 30 Apr 2025 14:15:04 -0400 Subject: [PATCH 16/16] Delete extra comments --- test-data/unit/check-tuples.test | 3 --- 1 file changed, 3 deletions(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 5401659137c5..1c6f78a2ce9f 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1856,6 +1856,3 @@ class tuple_aa_subclass(Tuple[A, A]): ... inst_tuple_aa_subclass: tuple_aa_subclass = tuple_aa_subclass((A(), A()))[:] # E: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "tuple_aa_subclass") [builtins fixtures/tuple.pyi] - --- --- --------------------