|
2 | 2 |
|
3 | 3 | from collections.abc import Iterable, Iterator |
4 | 4 | from contextlib import contextmanager |
| 5 | +from itertools import product |
5 | 6 | from typing import Any, Callable, Final, TypeVar, cast |
6 | 7 | from typing_extensions import TypeAlias as _TypeAlias |
7 | 8 |
|
|
34 | 35 | ) |
35 | 36 | from mypy.options import Options |
36 | 37 | from mypy.state import state |
| 38 | +from mypy.typeops import make_simplified_union |
37 | 39 | from mypy.types import ( |
38 | 40 | MYPYC_NATIVE_INT_NAMES, |
39 | 41 | TUPLE_LIKE_INSTANCE_NAMES, |
@@ -185,6 +187,27 @@ def is_subtype( |
185 | 187 | # steps we come back to initial call is_subtype(A, B) and immediately return True. |
186 | 188 | with pop_on_exit(type_state.get_assumptions(is_proper=False), left, right): |
187 | 189 | return _is_subtype(left, right, subtype_context, proper_subtype=False) |
| 190 | + left = get_proper_type(left) |
| 191 | + right = get_proper_type(right) |
| 192 | + |
| 193 | + # Special case: distribute Tuple unions before fallback subtype check |
| 194 | + if isinstance(left, TupleType) and isinstance(right, UnionType): |
| 195 | + items = [get_proper_type(item) for item in left.items] |
| 196 | + if any(isinstance(item, UnionType) for item in items): |
| 197 | + expanded = [] |
| 198 | + for item in items: |
| 199 | + if isinstance(item, UnionType): |
| 200 | + expanded.append(item.items) |
| 201 | + else: |
| 202 | + expanded.append([item]) |
| 203 | + distributed = [] |
| 204 | + for combo in product(*expanded): |
| 205 | + fb = left.partial_fallback |
| 206 | + if hasattr(left, "fallback") and left.fallback is not None: |
| 207 | + fb = left.fallback |
| 208 | + distributed.append(TupleType(list(combo), fallback=fb)) |
| 209 | + simplified = make_simplified_union(distributed) |
| 210 | + return _is_subtype(simplified, right, subtype_context, proper_subtype=False) |
188 | 211 | return _is_subtype(left, right, subtype_context, proper_subtype=False) |
189 | 212 |
|
190 | 213 |
|
|
0 commit comments