blob: 258100f264066dafb2d63be9cc472741ea5f377e [file] [edit]
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use crate::test::util::TestEnv;
use crate::testcase;
testcase!(
test_tuple,
r#"
from typing import assert_type, Literal
x = (1, "2")
assert_type(x, tuple[Literal[1], Literal["2"]])
y: tuple[int, Literal["3"]] = (1, "3")
"#,
);
testcase!(
test_index_literal,
r#"
from typing import assert_type, Literal
x = (1, "2")
assert_type(x[0], Literal[1])
assert_type(x[1], Literal["2"])
assert_type(x[-2], Literal[1])
assert_type(x[-1], Literal["2"])
"#,
);
testcase!(
test_invalid_ellipsis,
r#"
from typing import assert_type, Any
def test(
x1: tuple[int, ...], # OK
x2: tuple[...], # E: Invalid position for `...`
x3: tuple[int, ..., ...], # E: Invalid position for `...`
x4: tuple[int, ..., int], # E: Invalid position for `...`
x5: tuple[int, int, ...], # E: Invalid position for `...`
x6: tuple[..., int], # E: Invalid position for `...`
x7: tuple[*tuple[int], ...] # E: `...` cannot be used with an unpacked `TypeVarTuple` or tuple
):
assert_type(x2, tuple[Any, ...])
assert_type(x3, tuple[Any, ...])
assert_type(x4, tuple[Any, ...])
assert_type(x5, tuple[Any, ...])
assert_type(x6, tuple[Any, ...])
assert_type(x7, tuple[Any, ...])
"#,
);
testcase!(
test_index,
r#"
from typing import assert_type
def foo(x: tuple[int, str], y: tuple[int, ...], z: tuple[int, *tuple[str, ...], bool], idx: int) -> None:
assert_type(x[idx], int | str)
assert_type(y[idx], int)
assert_type(z[idx], bool | int | str)
x["nonsense"] # E: Cannot index into `tuple[int, str]`
y["nonsense"] # E: Cannot index into `tuple[int, ...]`
"#,
);
testcase!(
test_empty_tuple,
r#"
from typing import assert_type
assert_type((), tuple[()])
"#,
);
testcase!(
test_tuple_base,
r#"
from typing import Any
class Base1(tuple[Any, ...]): ...
class Base2(tuple[int, ...]): ...
class Base3(tuple[int, int]): ...
class Base4(tuple[str, int]): ...
class Child1(Base1, Base2): ...
class Child2(Base1, Base3): ...
class Child3(Base3, Base4): ... # E: Class `Child3` has inconsistent type arguments for base class `Iterable`
class Child4(Base2, Base3): ...
"#,
);
testcase!(
test_tuple_base_subtype,
r#"
from typing import *
class Size(tuple[int, ...]): ...
def f(x: tuple[int, ...]): ...
def g(x: Size):
f(x)
"#,
);
testcase!(
test_tuple_base_narrow,
r#"
from typing import *
class A(tuple[int, str]): ...
class B(tuple[int, str, bool]): ...
def test(x: A | B):
if len(x) == 2:
assert_type(x, A)
else:
assert_type(x, B)
"#,
);
testcase!(
test_tuple_base_index,
r#"
from typing import *
class A(tuple[int, str]): ...
def test(x: A):
assert_type(x[0], int)
assert_type(x[1], str)
"#,
);
testcase!(
test_unparameterized,
r#"
from typing import assert_type, Any, Tuple
def foo(x: tuple, y: Tuple) -> None:
assert_type(x, tuple[Any, ...])
assert_type(y, tuple[Any, ...])
"#,
);
testcase!(
test_tuple_type_attr_base,
r#"
from typing import Any
def is_namedtuple_cls(cls: Any):
if issubclass(cls, tuple):
print(cls.__bases__)
"#,
);
testcase!(
test_tuple_bad_unpack,
r#"
from typing import Any, Iterable
def f(x: int) -> int: ...
def test(y: int):
x: tuple[int, ...] = (3, *y, 4) # E: Expected an iterable, got `int`
x: tuple[int, ...] = (3, *y, f("x")) # E: Expected an iterable, got `int` # E: Argument `Literal['x']` is not assignable to parameter `x` with type `int` in function `f`
"#,
);
testcase!(
test_unpack_starred_not_iterable_in_tuple,
r#"
def test():
x: int = 42
y = (*x,) # E: Expected an iterable, got `int`
"#,
);
testcase!(
test_unpack_index_out_of_bounds,
r#"
def test(x: tuple[int]) -> None:
y, z = x # E: Cannot unpack
"#,
);
testcase!(
test_unpack_variadic_tuple_star,
r#"
from typing import assert_type
def test(t: tuple[int, *tuple[bool, ...], str]) -> None:
a, *rest, b = t
assert_type(a, int)
assert_type(rest, list[bool])
assert_type(b, str)
# A fixed suffix element that no after-star target consumes flows into the star,
# but the fixed prefix element does not smear in.
c, *carry = t
assert_type(c, int)
assert_type(carry, list[bool | str])
for x in t:
assert_type(x, int | bool | str)
"#,
);
testcase!(
test_unpack_variadic_tuple_empty_middle,
r#"
from typing import assert_type
# The unbounded middle can match zero elements, so a fixed target that indexes past
# its own end may land on a fixed element from the opposite end.
def prefix_only(x: tuple[str, *tuple[int, ...]]) -> None:
*head, last = x
assert_type(head, list[int | str])
assert_type(last, int | str)
def suffix_only(x: tuple[*tuple[int, ...], str]) -> None:
first, *rest = x
assert_type(first, int | str)
assert_type(rest, list[int | str])
"#,
);
testcase!(
test_unpack_variadic_tuple_exact_length,
r#"
from typing import assert_type
# An unpack with no star pins the length exactly, so the variadic middle has a known
# size and each target resolves to a single element -- no fixed-end smearing.
def f(t: tuple[int, *tuple[bool, ...], str]) -> None:
a, b = t # length must be 2, so the middle is empty
assert_type(a, int)
assert_type(b, str)
c, d, e = t # length must be 3, so the middle has exactly one element
assert_type(c, int)
assert_type(d, bool)
assert_type(e, str)
match t:
case [f, g]:
assert_type(f, int)
assert_type(g, str)
"#,
);
testcase!(
test_unpack_variadic_tuple_too_few_targets,
r#"
# The fixed prefix and suffix guarantee at least 2 elements, so an exact unpack into
# fewer targets can never succeed.
def f(t: tuple[int, *tuple[bool, ...], str]) -> None:
(a,) = t # E: Cannot unpack tuple[int, *tuple[bool, ...], str] (of size 2+) into 1 value
"#,
);
testcase!(
test_unpack_variadic_tuple_both_ends_mandatory,
r#"
from typing import assert_type
# `b` and `c` are mandatory targets that reserve the fixed prefix `int` and suffix `str`,
# so the empty-middle shift must not leak those into each other or into the star.
def f(t: tuple[int, *tuple[bool, ...], str]) -> None:
a, b, *rest, c = t
assert_type(a, int)
assert_type(b, bool)
assert_type(rest, list[bool])
assert_type(c, str)
"#,
);
testcase!(
test_match_variadic_tuple_both_ends_mandatory,
r#"
from typing import assert_type
def f(t: tuple[int, *tuple[bool, ...], str]) -> None:
match t:
case [a, b, *rest, c]:
assert_type(b, bool)
assert_type(c, str)
"#,
);
testcase!(
test_unpack_variadic_tuple_bad_star,
r#"
def f(t: tuple[int, *int, str]) -> None: ... # E: Expected a type form, got instance of `*int`
def g(t: tuple[int, *list[int], str]) -> None: ... # E: Expected a type form, got instance of `*list[int]`
"#,
);
testcase!(
test_unpack_in_literal,
r#"
from typing import Any, assert_type, Literal
def test(x: tuple[int, ...], y: str) -> None:
assert_type(("foo", *(1, 1)), tuple[Literal['foo'], Literal[1], Literal[1]])
assert_type((1, *x, 2), tuple[Literal[1], *tuple[int, ...], Literal[2]])
assert_type((1, *x, *x, 3), tuple[Literal[1], *tuple[int, ...], Literal[3]])
assert_type((1, *x, y, *x, 3), tuple[Literal[1], *tuple[int | str, ...], Literal[3]])
"#,
);
testcase!(
test_unbounded_solve,
r#"
from typing import Any
def test(x: tuple[int, str], y: tuple[int, ...], z: tuple[Any, ...]) -> None:
a: tuple[int, int] = z
b: tuple[int | str, ...] = x
c: tuple[int | str, ...] = y
d: tuple[int, ...] = x # E: `tuple[int, str]` is not assignable to `tuple[int, ...]`
"#,
);
testcase!(
test_unpacked_solve,
r#"
from typing import Any
def test(a: tuple[int, bool, str], b: tuple[Any, ...], c: tuple[int, *tuple[bool, ...], str]) -> None:
x1: tuple[int, *tuple[bool, ...], str] = a
x2: tuple[int, *tuple[bool | str, ...]] = a
x3: tuple[*tuple[int | bool, ...], str] = a
x4: tuple[int, bool, *tuple[str, ...]] = a
x5: tuple[*tuple[int, ...], bool, str] = a
x6: tuple[int, *tuple[bool, ...], str] = b
x7: tuple[int, *tuple[bool, ...], str] = c
x8: tuple[int, *tuple[bool | str, ...]] = c
x9: tuple[*tuple[int | bool, ...], str] = c
x10: tuple[*tuple[int], *tuple[bool], *tuple[str]] = a
x11: tuple[int, *tuple[bool, str]] = a
x12: tuple[*tuple[int, bool, str]] = a
x13: tuple[*tuple[int, ...], *tuple[bool], *tuple[str]] = a
x14: tuple[*tuple[int, ...], *tuple[bool, ...], *tuple[str]] = a # E: Only one unbounded type is allowed to be unpacked
"#,
);
testcase!(
test_slice_literal,
r#"
from typing import assert_type, Literal
x = (5, 6, 7)
assert_type(x[0:0], tuple[()])
assert_type(x[0:1], tuple[Literal[5]])
assert_type(x[0:2], tuple[Literal[5], Literal[6]])
assert_type(x[0:3], tuple[Literal[5], Literal[6], Literal[7]])
assert_type(x[1:1], tuple[()])
assert_type(x[1:2], tuple[Literal[6]])
assert_type(x[1:3], tuple[Literal[6], Literal[7]])
assert_type(x[2:2], tuple[()])
assert_type(x[2:3], tuple[Literal[7]])
assert_type(x[3:3], tuple[()])
assert_type(x[:0], tuple[()])
assert_type(x[:1], tuple[Literal[5]])
assert_type(x[:2], tuple[Literal[5], Literal[6]])
assert_type(x[:3], tuple[Literal[5], Literal[6], Literal[7]])
assert_type(x[0:], tuple[Literal[5], Literal[6], Literal[7]])
assert_type(x[1:], tuple[Literal[6], Literal[7]])
assert_type(x[2:], tuple[Literal[7]])
assert_type(x[3:], tuple[()])
"#,
);
testcase!(
test_slice_negative,
r#"
from typing import assert_type, Literal
x = (5, 6, 7)
# Negative end index
assert_type(x[:-1], tuple[Literal[5], Literal[6]])
assert_type(x[:-2], tuple[Literal[5]])
assert_type(x[:-3], tuple[()])
# Negative start index
assert_type(x[-1:], tuple[Literal[7]])
assert_type(x[-2:], tuple[Literal[6], Literal[7]])
assert_type(x[-3:], tuple[Literal[5], Literal[6], Literal[7]])
# Both negative
assert_type(x[-3:-1], tuple[Literal[5], Literal[6]])
assert_type(x[-2:-1], tuple[Literal[6]])
# Mixed positive and negative
assert_type(x[0:-1], tuple[Literal[5], Literal[6]])
assert_type(x[1:-1], tuple[Literal[6]])
assert_type(x[-2:3], tuple[Literal[6], Literal[7]])
"#,
);
testcase!(
test_unbounded_tuple_hint,
r#"
x1: tuple[str, ...] = ("ok",)
x2: tuple[int, ...] = ("err",) # E: `tuple[Literal['err']]` is not assignable to `tuple[int, ...]`
"#,
);
testcase!(
test_superclass_tuple_hint,
r#"
from typing import Iterable, Literal
x1: Iterable[Literal['ok']] = ("ok",)
x2: Iterable = ("ok",)
x3: object = ("ok",)
x4: Iterable[int] = ("err",) # E: `tuple[Literal['err']]` is not assignable to `Iterable[int]`
x5: list[int] = ("err",) # E: `tuple[Literal['err']]` is not assignable to `list[int]`
"#,
);
testcase!(
test_empty_tuple_hint,
r#"
from typing import Iterable
x: Iterable[str] = ()
"#,
);
testcase!(
test_unpack_union,
r#"
from typing import assert_type
def f() -> tuple[int, str] | tuple[bool, ...]: ...
(x, y) = f()
assert_type(x, int | bool)
assert_type(y, str | bool)
(x, y, z) = f() # E: Cannot unpack
"#,
);
testcase!(
test_iterate_union,
r#"
from typing import assert_type
def f() -> tuple[int, str] | tuple[bool, ...]: ...
for x in f():
assert_type(x, int | bool | str)
"#,
);
testcase!(
test_tuple_parent,
r#"
from typing import Any, assert_type
class C1(tuple[int, ...]):
pass
class C2(tuple[int, int]):
pass
for x in C1():
assert_type(x, int)
for x in C2():
assert_type(x, int)
"#,
);
testcase!(
test_tuple_short_unpack,
r#"
*a, b, c = (1,) # E: Cannot unpack tuple[Literal[1]] (of size 1) into 2+ values
"#,
);
testcase!(
test_tuple_with_never_element_preserves_shape,
r#"
from typing import Literal, NoReturn, assert_type
def f(x: NoReturn) -> None:
t = (x, 1)
assert_type(t[1], Literal[1])
"#,
);
testcase!(
test_unpacked_tuple_subtype,
r#"
from typing import Sequence
def test[*Ts](x1: tuple[int, *tuple[str, ...]], x2: tuple[*Ts]) -> None:
y1: Sequence[int | str] = x1
y2: tuple[int | str, ...] = x1
y3: tuple[object, ...] = x2
"#,
);
testcase!(
test_unpack_typevar_bound_to_tuple,
r#"
from typing import assert_type
def f[Z: tuple[str, int]](x: Z):
u, v = x
assert_type(u, str)
assert_type(v, int)
"#,
);
testcase!(
test_unpack_typevar_bound_to_tuple_three_elements,
r#"
from typing import assert_type
def f[Z: tuple[str, int, bytes]](x: Z):
a, b, c = x
assert_type(a, str)
assert_type(b, int)
assert_type(c, bytes)
"#,
);
testcase!(
test_unpack_typevar_bound_to_unbounded_tuple,
r#"
from typing import assert_type
def f[Z: tuple[int, ...]](x: Z):
a, b = x
assert_type(a, int)
assert_type(b, int)
"#,
);
testcase!(
test_unpack_typevar_bound_to_tuple_starred,
r#"
from typing import assert_type
def f[Z: tuple[str, int, bytes]](x: Z):
a, *b = x
assert_type(a, str)
assert_type(b, list[bytes | int])
"#,
);
testcase!(
test_unpack_unpacked_tuple_assignment,
r#"
from typing import assert_type
def f(x: tuple[str, *tuple[int, ...]]) -> str:
head, *tail = x
assert_type(head, str)
assert_type(tail, list[int])
return head
"#,
);
testcase!(
test_unpack_constrained_typevar_tuple,
r#"
from typing import TypeVar, assert_type
Z = TypeVar("Z", tuple[str, int], tuple[bool, bytes])
def f(x: Z):
a, b = x
assert_type(a, bool | str)
assert_type(b, bytes | int)
"#,
);
testcase!(
test_unpack_typevar_unbounded_not_iterable,
r#"
def f[Z](x: Z):
a, b = x # E: Type `object` is not iterable
"#,
);
testcase!(
test_unpack_typevar_bound_not_iterable,
r#"
def f[Z: int](x: Z):
a, b = x # E: Type `int` is not iterable
"#,
);
testcase!(
test_tuple_slice_non_literal,
r#"
from typing import assert_type
def test(x: tuple[int, str, bool], y: tuple[int, ...], start: int, stop: int, step: int):
assert_type(x[start:stop:step], tuple[int | str | bool, ...])
assert_type(y[start:stop:step], tuple[int, ...])
"#,
);
testcase!(
test_slice_invalid_components,
r#"
def test(xs: list[int], ys: tuple[int, ...]) -> None:
xs[1.5:] # E: Slice indices must be integers or have an `__index__` method
ys[:1.5] # E: Slice indices must be integers or have an `__index__` method
xs[::1.5] # E: Slice indices must be integers or have an `__index__` method
ys[::1.5] # E: Slice indices must be integers or have an `__index__` method
"#,
);
testcase!(
test_slice_accepts_index_method,
r#"
class Index:
def __index__(self) -> int:
return 0
def test(xs: list[int], ys: tuple[int, ...], index: Index) -> None:
xs[index:]
ys[:index]
xs[::index]
"#,
);
testcase!(
test_slice_zero_step,
r#"
def test(xs: list[int], ys: tuple[int, ...]) -> None:
xs[::0] # E: Slice step cannot be zero
ys[::0] # E: Slice step cannot be zero
"#,
);
testcase!(
test_slice_omitted_positions_preserved,
r#"
from typing import assert_type
def test(x: tuple[int, str, bool]) -> None:
assert_type(x[:2], tuple[int, str])
assert_type(x[1:], tuple[str, bool])
"#,
);
testcase!(
test_tuple_reverse_slice,
r#"
from typing import assert_type
def test(x: tuple[int, str, bool]) -> None:
assert_type(x[::-1], tuple[bool, str, int])
"#,
);
testcase!(
test_slice_union_step_reported_once,
r#"
def test(xs: list[int] | tuple[int, ...]) -> None:
xs[::0] # E: Slice step cannot be zero
xs[::1.5] # E: Slice indices must be integers or have an `__index__` method
"#,
);
testcase!(
test_slice_subset,
r#"
def f(x: slice) -> None:
pass
def g(x: slice[int, int, int]) -> None:
f(x)
"#,
);
testcase!(
test_tuple_constructor,
r#"
from typing import Any, Iterable
def test(y: Iterable[Any], z: Iterable[int]):
x: tuple[int, int] = tuple(y)
x = tuple(z) # E: `tuple[int, ...]` is not assignable to variable `x` with type `tuple[int, int]`
"#,
);
testcase!(
test_tuple_constructor_assert_type,
r#"
from typing import assert_type, Iterable
def test(x: Iterable[int]) -> None:
assert_type(tuple(x), tuple[int, ...])
"#,
);
testcase!(
bug = "TODO: handle generator from fixed-length heterogeneous iterable",
test_tuple_constructor_preserves_fixed_length,
r#"
from typing import assert_type
def test(xs: tuple[int, int]) -> None:
ys: tuple[int, int] = tuple(xs)
assert_type(tuple(xs), tuple[int, int])
zs: tuple[int, int] = tuple(x for x in xs) # E: `tuple[int, ...]` is not assignable to `tuple[int, int]`
"#,
);
testcase!(
test_tuple_constructor_concat,
r#"
from typing import assert_type, Iterable, Literal
def test(x: Iterable[int]) -> None:
assert_type(tuple(x) + (3,), tuple[*tuple[int, ...], Literal[3]])
"#,
);
testcase!(
test_namedtuple_constructor_nominal,
r#"
from typing import NamedTuple, assert_type
class Point(NamedTuple):
x: int
y: int
p = Point(1, 2)
assert_type(p, Point)
"#,
);
testcase!(
test_tuple_subclass_constructor_nominal,
r#"
from typing import assert_type
class MyTuple(tuple[int, ...]): pass
m = MyTuple([1, 2])
assert_type(m, MyTuple)
"#,
);
testcase!(
test_star_unpack_single_unbounded_tuple,
r#"
from typing import assert_type
def test(x: tuple[int, ...]) -> None:
y = (*x,)
"#,
);
testcase!(
test_star_unpack_union_of_tuples,
r#"
from typing import assert_type
def f() -> tuple[int, ...] | tuple[str, ...]:
...
x = (*f(),)
"#,
);
testcase!(
test_tuple_aug_assign,
r#"
def test() -> None:
x: tuple[object, ...] = (1,)
x += (2, "y")
y: tuple[int, ...] = (1,)
y += (2, "y") # E: Augmented assignment result `tuple[*tuple[int, ...], Literal[2], Literal['y']]` is not assignable to `tuple[int, ...]`
"#,
);
testcase!(
test_tuple_concat,
r#"
from typing import assert_type
def test(x: tuple[int, str], y: tuple[bool, ...], z: tuple[int, *tuple[str, ...], bool]) -> None:
assert_type(x + x, tuple[int, str, int, str])
assert_type(x + y, tuple[int, str, *tuple[bool, ...]])
assert_type(x + z, tuple[int, str, int, *tuple[str, ...], bool])
assert_type(y + x, tuple[*tuple[bool, ...], int, str])
assert_type(y + y, tuple[bool, ...])
assert_type(y + z, tuple[*tuple[bool | int | str, ...], bool])
assert_type(z + x, tuple[int, *tuple[str, ...], bool, int, str])
assert_type(z + y, tuple[int, *tuple[str | bool, ...]])
assert_type(z + z, tuple[int, *tuple[str | bool | int, ...], bool])
"#,
);
testcase!(
test_tuple_concat_union,
r#"
from typing import assert_type
def test(x: tuple[int] | tuple[str]) -> None:
assert_type(x + x, tuple[int, int] | tuple[str, str] | tuple[int, str] | tuple[str, int])
"#,
);
testcase!(
test_tuple_repeat,
r#"
from typing import assert_type, Literal
assert_type((42,) * 2, tuple[Literal[42], Literal[42]])
assert_type(2 * (42,), tuple[Literal[42], Literal[42]])
assert_type((1, "x") * 2, tuple[Literal[1], Literal["x"], Literal[1], Literal["x"]])
assert_type((1,) * 0, tuple[()])
assert_type((1,) * -1, tuple[()])
assert_type((1,) * 257, tuple[Literal[1], ...])
"#,
);
testcase!(
test_unpack_tuple_with_double_def,
r#"
from typing import Unpack, Any
def f(*args: Unpack[tuple[Any, ...]]):
pass
def f():
pass
"#,
);
testcase!(
test_tuple_equivalence,
r#"
from typing import assert_type
def f(x: tuple):
assert_type(x, tuple)
def g(x):
if isinstance(x, tuple):
assert_type(x, tuple)
"#,
);
#[test]
fn test_tuple_concat_large_union_no_crash() -> anyhow::Result<()> {
let code = r#"
a: int | list[int] | tuple[int, ...] | bool
a + (a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a)
"#;
let (state, handle_fn) = TestEnv::one("main", code).to_state();
let handle = handle_fn("main");
state.transaction().get_errors(&[handle]);
Ok(())
}
testcase!(
test_tuple_class_type,
r#"
from typing import Any
def f(x: type[tuple[Any, ...]]):
return x() # Ok
"#,
);
testcase!(
test_bad_tuple_index,
r#"
def f(x: tuple[int, int], y: tuple[int, ...]):
x[(1, 2)] # E: No matching overload found for function `tuple.__getitem__`
y[(1, 2)] # E: No matching overload found for function `tuple.__getitem__`
"#,
);
testcase!(
test_typevartuple_subclass_index,
r#"
from typing import assert_type, TypeVarTuple
Ts = TypeVarTuple('Ts')
class TupleChild(tuple[*Ts]): ...
def f(x: TupleChild[int, str]):
assert_type(x[0], int)
assert_type(x[1], str)
x[2] # E: Index 2 out of range for tuple with 2 elements
"#,
);
testcase!(
test_tuple_subclass_getitem_override,
r#"
from typing import assert_type
class Foo(tuple[int, ...]):
def __getitem__(self, name: str) -> int: # E: `Foo.__getitem__` has type
...
def test(foo: Foo) -> None:
assert_type(foo["test"], int)
"#,
);
testcase!(
test_tuple_subclass_inherited_getitem_override,
r#"
from typing import assert_type
class Parent(tuple[int, ...]):
def __getitem__(self, name: str) -> int: # E: `Parent.__getitem__` has type
...
class Child(Parent):
pass
def test(c: Child) -> None:
assert_type(c["test"], int)
"#,
);
testcase!(
test_starred_empty_tuple_no_panic,
r#"
(),*()
"#,
);
// https://github.com/facebook/pyrefly/issues/273
// https://discuss.python.org/t/unbounded-tuple-unions/92472
testcase!(
test_union_empty_tuple_and_variadic_tuple,
r#"
type Eq0 = tuple[()]
type Eq1 = tuple[int]
type Ge0 = tuple[int, ...]
type Ge1 = tuple[int, *Ge0]
def test(eq0: Eq0, eq1: Eq1, ge0: Ge0, ge1: Ge1) -> None:
eq0_ge1__eq0: Eq0 | Ge1 = eq0
eq0_ge1__eq1: Eq0 | Ge1 = eq1
eq0_ge1__ge0: Eq0 | Ge1 = ge0
eq0_ge1__ge1: Eq0 | Ge1 = ge1
"#,
);
testcase!(
test_giant_tuple_literal,
r#"
# literal tuples with >256 elements get inferred as `tuple[Any, ...]`
from typing import assert_type, Any
x = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256)
assert_type(x, tuple[Any, ...])
"#,
);
testcase!(
test_assign_unknown_tuple_to_concrete_tuple,
r#"
def f(x):
y: tuple[float, float] = tuple(x)
"#,
);
testcase!(
test_assign_varlength_tuple_to_concrete_tuple_error,
r#"
from typing import Any, Iterable
def f(x: Iterable[float]):
y: tuple[float, float] = tuple(x) # E: `tuple[float, ...]` is not assignable to `tuple[float, float]`
"#,
);
testcase!(
test_tuple_iterable_mismatch,
r#"
from typing import Iterable
def f(x: tuple[str, ...]): ...
def g(x: Iterable[int]):
f(tuple(x)) # E: `tuple[int, ...]` is not assignable to parameter `x` with type `tuple[str, ...]`
"#,
);
testcase!(
test_tuple_constraint_mismatch,
r#"
def f[T: (int, str)](x: tuple[T, ...], y: tuple[T, T]):
pass
f((1, 2), ("", "")) # E: `tuple[Literal[''], Literal['']]` is not assignable to parameter `y` with type `tuple[int, int]`
"#,
);
testcase!(
test_hint_influences_tuple_type,
r#"
from typing import Literal
CONSTS = ("a", "b")
x: tuple[Literal["a", "b"], ...] = tuple(CONSTS)
"#,
);
testcase!(
test_callable_tuple_mismatch,
r#"
from typing import Callable
def make_tuple[T](x: T) -> tuple[T, ...]:
return (x,)
f: Callable[[int], tuple[int, str]] = make_tuple # E: `[T](x: T) -> tuple[T, ...]` is not assignable to `(int) -> tuple[int, str]`
"#,
);
// Regression test: widening wide tuple unions keeps type complexity linear at control-flow joins.
// Without it, conditionally appending to a tuple produces an exponential number of concrete tuple
// variants and causes a OOM
testcase!(
test_many_conditional_tuple_appends,
r#"
class D0: pass
class D1: pass
class D2: pass
class D3: pass
class D4: pass
class D5: pass
class D6: pass
class D7: pass
class D8: pass
class D9: pass
class D10: pass
class D11: pass
class D12: pass
class D13: pass
class D14: pass
class D15: pass
class D16: pass
class D17: pass
class D18: pass
class D19: pass
class D20: pass
class D21: pass
class D22: pass
class D23: pass
class D24: pass
class D25: pass
class D26: pass
class D27: pass
class D28: pass
class D29: pass
class D30: pass
class D31: pass
class D32: pass
class D33: pass
class D34: pass
class D35: pass
class D36: pass
class D37: pass
class D38: pass
class D39: pass
class D40: pass
class D41: pass
class D42: pass
class D43: pass
class D44: pass
class D45: pass
class D46: pass
class D47: pass
class D48: pass
class D49: pass
class D50: pass
class D51: pass
class D52: pass
class D53: pass
class D54: pass
class D55: pass
class D56: pass
class D57: pass
class D58: pass
class D59: pass
class D60: pass
class D61: pass
class D62: pass
class D63: pass
class D64: pass
def repro(conds: list[bool]):
z = ()
if conds[0]: z += (D0(),)
if conds[1]: z += (D1(),)
if conds[2]: z += (D2(),)
if conds[3]: z += (D3(),)
if conds[4]: z += (D4(),)
if conds[5]: z += (D5(),)
if conds[6]: z += (D6(),)
if conds[7]: z += (D7(),)
if conds[8]: z += (D8(),)
if conds[9]: z += (D9(),)
if conds[10]: z += (D10(),)
if conds[11]: z += (D11(),)
if conds[12]: z += (D12(),)
if conds[13]: z += (D13(),)
if conds[14]: z += (D14(),)
if conds[15]: z += (D15(),)
if conds[16]: z += (D16(),)
if conds[17]: z += (D17(),)
if conds[18]: z += (D18(),)
if conds[19]: z += (D19(),)
if conds[20]: z += (D20(),)
if conds[21]: z += (D21(),)
if conds[22]: z += (D22(),)
if conds[23]: z += (D23(),)
if conds[24]: z += (D24(),)
if conds[25]: z += (D25(),)
if conds[26]: z += (D26(),)
if conds[27]: z += (D27(),)
if conds[28]: z += (D28(),)
if conds[29]: z += (D29(),)
if conds[30]: z += (D30(),)
if conds[31]: z += (D31(),)
if conds[32]: z += (D32(),)
if conds[33]: z += (D33(),)
if conds[34]: z += (D34(),)
if conds[35]: z += (D35(),)
if conds[36]: z += (D36(),)
if conds[37]: z += (D37(),)
if conds[38]: z += (D38(),)
if conds[39]: z += (D39(),)
if conds[40]: z += (D40(),)
if conds[41]: z += (D41(),)
if conds[42]: z += (D42(),)
if conds[43]: z += (D43(),)
if conds[44]: z += (D44(),)
if conds[45]: z += (D45(),)
if conds[46]: z += (D46(),)
if conds[47]: z += (D47(),)
if conds[48]: z += (D48(),)
if conds[49]: z += (D49(),)
if conds[50]: z += (D50(),)
if conds[51]: z += (D51(),)
if conds[52]: z += (D52(),)
if conds[53]: z += (D53(),)
if conds[54]: z += (D54(),)
if conds[55]: z += (D55(),)
if conds[56]: z += (D56(),)
if conds[57]: z += (D57(),)
if conds[58]: z += (D58(),)
if conds[59]: z += (D59(),)
if conds[60]: z += (D60(),)
if conds[61]: z += (D61(),)
if conds[62]: z += (D62(),)
if conds[63]: z += (D63(),)
if conds[64]: z += (D64(),)
return z
"#,
);
// Erasing a tuple to `builtins.tuple[...]` for a subset check unions the element types. The
// union goes through the solver, so a pair of complementary bool literals collapses to `bool`
// rather than remaining a literal union.
testcase!(
test_tuple_erasure_unions_bool_literals,
r#"
from typing import Literal, Sequence, TypeVar, assert_type
T = TypeVar("T")
def f(s: Sequence[T]) -> T: ...
def g(a: Literal[True], b: Literal[False]) -> None:
assert_type(f((a, b)), bool)
"#,
);
// The same erasure absorbs `LiteralString` into `str`. `LiteralString | str` and `str` are
// equivalent, so only the rendered form differs and `reveal_type` is what can observe it.
testcase!(
test_tuple_erasure_unions_literal_string_with_str,
r#"
from typing import LiteralString, Sequence, TypeVar, reveal_type
T = TypeVar("T")
def f(s: Sequence[T]) -> T: ...
def g(a: LiteralString, b: str) -> None:
reveal_type(f((a, b))) # E: revealed type: str
"#,
);
// When the members cover every enum member, the erased element type is the enum class.
testcase!(
test_tuple_erasure_promotes_exhaustive_enum,
r#"
from enum import Enum
from typing import Literal, Sequence, TypeVar, assert_type
class E(Enum):
A = 1
B = 2
T = TypeVar("T")
def f(s: Sequence[T]) -> T: ...
def g(a: Literal[E.A], b: Literal[E.B]) -> None:
assert_type(f((a, b)), E)
"#,
);
// An unpacked tuple takes the same erasure path as a concrete one.
testcase!(
test_tuple_erasure_unions_unpacked_elements,
r#"
from typing import Literal, Sequence, TypeVar, assert_type
T = TypeVar("T")
def f(s: Sequence[T]) -> T: ...
def g(a: Literal[True], rest: tuple[Literal[False], ...]) -> None:
assert_type(f((a, *rest)), bool)
"#,
);